ignislabs / flare-cqrs
Flare 是一个小巧易用的 CQRS 库。
Requires
- php: >=7.1
Requires (Dev)
- phpspec/phpspec: ^3.2
- psr/container: ^1.0@dev
This package is not auto-updated.
Last update: 2024-09-15 01:16:49 UTC
README
Flare 是一个小巧易用的 CQRS 库。
Flare 通过利用消息总线模式来驱动 CQRS,将查询(询问消息)与命令(强制消息)分开。
此库深受 Mathias Verraes 的消息类型文章的启发。
什么是 CQRS?
起源于 Bertrand Meyer 的《命令查询分离》(CQS)原则
它指出,每个方法应该要么是执行动作的命令,要么是返回数据的查询,但不能两者都是。换句话说,提问不应该改变答案。更正式地说,方法只有在它们是引用透明的并且因此没有副作用的情况下才应返回值。
因此,命令查询责任分离(CQRS)
... 通过使用单独的查询和命令对象来分别检索和修改数据,应用了 CQS 原则。
来源: 维基百科
安装
通过 composer
$ composer require ignislabs/flare-cqrs
用法
FlareCQRS 是框架无关的,但启动和使用它非常容易。
启动
<?php // Instantiate the resolver: // This will get you a new instance of the hander based on the _handler id_, // i.e, the fully-qualified class name, or wharever identifier you might use in // a framework container. // By default FlareCQRS comes with two resolvers, a CallableResolver and a PSR11Resolver. // You can always create your own resolver by implementing the `Resolver` contract. // Generic callable resolver $resolver = new \IgnisLabs\FlareCQRS\Handler\Resolver\CallableResolver(function($handlerId) { // Somehow resolve your handler handler: return new performMagicToGetHandlerInstance($handlerId); }); // PSR11 resolver (assuming Laravel's `$app` container, since it's PSR-11 compliant) $resolver = new \IgnisLabs\FlareCQRS\Handler\Resolver\PSR11Resolver($app); // Now instantiate the buses passing them a Locator instance $queryBus = new \IgnisLabs\FlareCQRS\QueryBus( // Tell the Locator which handler corresponds to which query // and how to instantiate the handlers (passing in the Resolver) new \IgnisLabs\FlareCQRS\Handler\Locator\MapLocator($resolver, [ GetAllTasksQuery::class => GetAllTasksHandler::class ]) ); $commandBus = new \IgnisLabs\FlareCQRS\CommandBus( // Tell the Locator which handler corresponds to which command // and how to instantiate the handlers (passing in the Resolver) new \IgnisLabs\FlareCQRS\Handler\Locator\MapLocator($resolver, [ AddTaskCommand::class => AddTaskHandler::class ]) );
用法
现在您可以使用总线来分派任何消息,例如
<?php // Queries can return whatever you need, it will be encapsulated in a Result object $result = $queryBus->dispatch(new GetAllTasksQuery('some', 'params')); // You can call `$result->call`: $result->call(function(TaskCollection $tasks) { // Do what you want with your results // Using `call` let's you use type-hinting // It can be a any `callable`, not just a closure }); // Or just get the result right away: $result->getResult(); // Commands do not return anything $commandBus->dispatch(new AddTaskCommand('Task Title', 'The task description')); // You can dispatch multiple commands in sequence with a single call $commandBus->dispatch( new AddTaskCommand('Task Title', 'The task description'), new UpdateTaskCommand('NEW Task Title', 'The NEW task description') ); // Or if you like splat! $commandBus->dispatch(...$commandsArray);
消息(查询 & 命令)类
您的消息类是简单的 DTO 对象,因此没有规则或合同可以使用,它们可以是你想要的任何东西。
然而,您可以利用 DataAccessorTrait
。有了它,您可以为您的消息类拥有自动的访问器属性。特质定义了一个 data
私有属性,一个 get
访问器方法以及 __get
魔法方法,这样您就可以将数据作为属性访问
<?php class MyMessage { use \IgnisLabs\FlareCQRS\Message\DataAccessorTrait; public function __construct(string $foo, int $bar) { $this->setData(compact('foo', 'bar')); } } $message = new MyMessage('baz', 'qux'); // Using generic `get` accessor: $message->get('foo'); // returns 'baz' // Using the magic accessor: $message->bar; // returns 'qux'
处理器
处理器可以是任何东西,只要它是 callable
。尽管如此,您可能希望将它们做成类。为了使一个类可调用,只需在其中实现 __invoke
方法即可。
__invoke
方法将接收相应命令的实例。
这样,类就是 100% 您的,根本不依赖此库,您可以自由地使用类型提示。
让我们看一个快速示例
<?php class MyMessageHandler { public function __invoke(MyMessage $command) { // do something here } }
中间件
您可以创建中间件来在消息到达各自的处理器之前与之交互。
中间件,就像处理器一样,是 callable
,但您可能更喜欢将它们做成类,使用相同的 __invoke
策略。
您可以在实例化时将您的中间件作为最后一个参数传递给总线
<?php $callableMiddleware = function() { /* ... */ }; $queryBus = new \IgnisLabs\FlareCQRS\QueryBus( $locator, new LoggingMiddleware($logger), new FooMiddleware, $callableMiddleware );
或者您可以在一次性的基础上添加/替换中间件
<?php // Add a middleware to the chain $commandBus->addMiddleware(new LoggingMiddleware($logger)); // Completely replace the middleware chain $commandBus->middlewares(new LoggingMiddleware($logger), new FooMiddleware);
总线是 不可变 的,因此在总线上添加或替换中间件将始终返回一个新的总线实例,因此对总线的后续调用不会受到影响。