bartoszbartniczak / cqrs
在PHP中应用Command Query Responsibility Segregation模式。
Requires
Requires (Dev)
- phpunit/phpunit: 5.*
This package is not auto-updated.
Last update: 2022-02-01 13:04:23 UTC
README
PHP中的命令查询责任分离
目录
前言
如果你不知道命令查询责任分离是什么,你应该阅读由Martin Fowler撰写的一篇非常好的文章。要阅读文章,请点击这里。
此库是该模式在PHP中的实现。
以下描述了库的主要组件。
组件
命令
命令通常表示一些领域逻辑。它可以包含数据验证、数据处理等。命令的结果通常保存在数据库中。例如,RegisterUser、SendEmail等。你应该将命令视为“数据持有者”。在构造函数参数中,你应该传递处理命令所需的所有数据。
查询
查询是一种特殊的命令。它在存储库中查找数据,并将结果作为返回。例如,FindUser、FindProduct等。
命令处理器
它处理命令。在这个对象中,你应该使用在命令构造函数中传递的参数。在这个对象中,你可以验证、更改和处理数据。命令处理器可以在命令总线上注册。参见章节[#how-to-register-commandhandler]。
示例
<?php use BartoszBartniczak\CQRS\Command\Command; use BartoszBartniczak\CQRS\Command\Handler\CommandHandler; interface EmailSenderService { public function sendEmail(string $receiver, string $subject, string $body); } class FakeEmailSenderService implements EmailSenderService { public function sendEmail(string $receiver, string $subject, string $body) { // TODO: Here you should send email! } } class SendEmailCommand implements Command { /** * @var string */ private $receiver; /** * @var string */ private $subject; /** * @var string */ private $body; /** * @var EmailSenderService */ private $emailSenderService; /** * SendEmail constructor. * @param string $receiver * @param string $subject * @param string $body * @param EmailSenderService $emailSenderService */ public function __construct(string $receiver, string $subject, string $body, EmailSenderService $emailSenderService) { $this->receiver = $receiver; $this->subject = $subject; $this->body = $body; $this->emailSenderService = $emailSenderService; } /** * @return string */ public function getReceiver(): string { return $this->receiver; } /** * @return string */ public function getSubject(): string { return $this->subject; } /** * @return string */ public function getBody(): string { return $this->body; } /** * @return EmailSenderService */ public function getEmailSenderService(): EmailSenderService { return $this->emailSenderService; } } class SendEmailHandler extends CommandHandler { public function handle(Command $command) { //TODO: Add some validation here /* @var $command SendEmailCommand */ $command->getEmailSenderService()->sendEmail( $command->getReceiver(), $command->getSubject(), $command->getBody() ); } } $fakeEmailSenderService = new FakeEmailSenderService(); $sendEmailCommand = new SendEmailCommand('client@emial.com', 'Very important message!', 'Here is the body of the message.', $fakeEmailSenderService); $sendEmailHandler = new SendEmailHandler(); $sendEmailHandler->handle($sendEmailCommand);
命令总线
命令总线可以接收命令并使用命令处理器执行它们。为此,您需要注册命令处理器。
如何注册命令处理器?
use BartoszBartniczak\CQRS\Command\Bus\CommandBus; class SimpleCommandBus extends CommandBus{ protected function handleHandlerException(CommandHandler $handler) { // TODO: Here you can react on HandlerException and then you should throw the CannotExecuteTheCommandException } protected function saveDataInRepository($data) { // TODO: Here you shoud persist the data } } $simpleCommandBus = new SimpleCommandBus(); $simpleCommandBus->registerHandler(SendEmailCommand::class, $sendEmailHandler);
现在您可以使用命令总线执行命令
try{ $simpleCommandBus->execute($sendEmailCommand); }catch(CannotExecuteTheCommandException $cannotExecuteTheCommandException){ // TODO: //Do some buisiness logic in here. }
命令是如何执行的?
在您传递命令以执行后,命令总线正在寻找适当的命令处理器来处理命令。如果命令处理器返回数据,它可能被保存在存储库中。命令处理器可以将其他命令传递给命令总线以进一步执行。
查询是如何执行的?
您可以将查询传递给命令总线进行执行。命令总线会寻找命令处理器。在handle()方法中,您可以在仓库中查找数据,然后作为结果返回。命令总线知道,在执行查询的结果中,您返回了一些结果,因此它将其保存到输出中。输出是execute()方法返回的结果。
示例
use BartoszBartniczak\CQRS\Command\Handler\CannotHandleTheCommandException; use BartoszBartniczak\CQRS\Command\Query; interface ProductRepository { /** * @param ProductId $productId * @return Product * @throws CannotFindProductException */ public function findProductById(ProductId $productId); } class FindProductInRepositoryCommand implements Query { /** * @var ProductRepository */ private $productRepository; /** * @var ProductId */ private $productId; /** * FindProductInRepositoryCommand constructor. * @param ProductId $productId * @param ProductRepository $productRepository */ public function __construct(ProductId $productId, ProductRepository $productRepository) { $this->productRepository = $productRepository; $this->productId = $productId; } /** * @return ProductRepository */ public function getProductRepository(): ProductRepository { return $this->productRepository; } /** * @return ProductId */ public function getProductId(): ProductId { return $this->productId; } } class FindProductInRepositoryHandler extends CommandHandler { public function handle(Command $command): Product { /* @var $command FindProductInRepositoryCommand */ try { $product = $command->getProductRepository()->findProductById( $command->getProductId() ); return $product; } catch (CannotFindProductException $cannotFindProductException) { //TODO: Do some buisiness logic in here. E.g. Save the wrong phrase/id for further computing. throw new CannotHandleTheCommandException("Product cannot be found.", null, $cannotFindProductException); } } }
测试
单元测试
要运行单元测试,请执行以下命令:
php vendor/phpunit/phpunit/phpunit --configuration tests/unit-tests/configuration.xml