beberlei / lite-cqrs
简单的命令-查询-责任分离库。
Requires (Dev)
- doctrine/common: >=2.0,<2.5
- doctrine/couchdb: dev-master
- doctrine/mongodb: dev-master
- doctrine/orm: >=2.0,<2.5
- jms/serializer-bundle: dev-master
- swiftmailer/swiftmailer: 4.*
- symfony/console: >=2.0,<2.2
- symfony/framework-bundle: >=2.0,<2.2
This package is auto-updated.
Last update: 2022-03-11 22:02:05 UTC
README
PHP 的基于命名规范的简单 CQRS 库(基于 C# 的 LiteCQRS,松散基于 LiteCQRS for C#),依赖于消息总线、命令、事件源和领域事件模式。
注意 使用 1.1 分支,因为 dev-master 目前正在进行重大重构。
术语
CQS 是命令-查询分离:一种模式,其中读取方法从不更改状态,写入方法从不返回数据。在此基础上,CQRS 建议将读取模型和写入模型分离,并使用 领域事件模式 来通知读取模型关于写入模型中的更改。
LiteCQRS 使用命令模式和中央消息总线服务,该服务找到相应的处理器来执行命令。命令只是一个具有一些属性的类,它可以可选地实现 LiteCQRS\Command
。
在执行命令期间,可以触发领域事件。这些事件也是一些具有一些属性的简单类,它们可以可选地实现 LiteCQRS\DomainEvent
。
事件队列知道在命令期间触发了哪些领域事件,然后将它们发布到事件消息总线,许多监听器可以监听它们。
更改
从 1.0 到 1.1
-
不再需要扩展
LiteCQRS\Command
和LiteCQRS\DomainEvent
。实际上,您可以使用任何类作为命令或事件。命名规范本身确保可以检测命令处理程序和事件监听器。 -
JMS 序列化插件不能再“分离”事件中作为事件一部分的聚合根属性。因此,将相关的聚合根放入事件中不再支持(即使使用 JMS Serializer 0.9 也不建议这样做)。
约定
- 命令处理程序类的所有公共方法都映射到命令 "Command Class Shortname" => "MethodName",当方法和命令类短名称匹配时。实现命令接口不是必需的(从 1.1 开始)
- 领域事件应用于事件处理程序 "Event Class Shortname" => "onEventClassShortname"。只有当匹配时,才会注册事件监听器。
- 领域事件应用于实体/聚合根 "Event Class Shortname" => "applyEventClassShortname"
- 您可以可选地扩展
DefaultDomainEvent
,它具有将数组输入映射到属性并如果传递未知属性则抛出异常的构造函数。 - 还有一个具有与
DefaultDomainEvent
相同语义的DefaultCommand
。扩展此不是必需的。
示例
HelloWorld\GreetingCommand
映射到注册处理程序上的greeting(GreetingCommand $command)
方法。HelloWorld\Commands\Greeting
映射到注册处理程序上的greeting(Greeting $command)
方法。HelloWorld\GreetedEvent
传递给所有具有方法onGreeted(GreetedEvent $event)
的事件处理程序。HelloWorld\Events\Greeted
传递给所有具有方法onGreeted(Greeted $event)
的事件处理程序。- 当在聚合根上创建时,
HelloWorld\GreetedEvent
将委派给applyGreeted($event)
。
安装与需求
使用1.1分支,因为dev-master当前正在进行大量重构。
核心库不依赖于其他库。插件依赖于它们特定的库。
使用Composer安装
{
"require": {
"beberlei/lite-cqrs": "1.1"
}
}
工作流程
这些是在执行期间命令经常通过LiteCQRS堆栈执行的步骤
- 您将命令推送到一个
CommandBus
。命令是您创建的扩展Command
的简单对象。 CommandBus
检查是否有可以执行您命令的处理程序。每个命令恰好有一个处理程序。- 命令处理程序改变领域模型的状态。它是通过创建事件(代表状态更改)并将它们传递给您的领域对象的
AggregateRoot::apply()
或DomainEventProvider::raise()
方法来做到的。 - 当命令完成时,命令总线将检查身份映射中的所有对象以查找事件。
- 所有找到的事件都将传递给
EventMessageBus#publish()
方法。 - 事件消息总线将所有事件分发给观察事件处理程序。
- 事件处理程序可以使用
CommandBus
再次创建新的命令。
命令和事件处理程序执行可以包装在管理事务的处理程序中。事件处理总是触发在任何命令事务之外。如果命令因任何异常失败,则将忘记/忽略由命令创建的所有事件。在这种情况下不会触发任何事件处理程序。
在InMemory CommandBus和EventMessageBus的情况下,LiteCQRS确保命令和事件处理程序的执行永远不会嵌套,而是按顺序线性化。这防止了每个命令的独立事务相互影响。
示例
请参阅examples/以获取一些示例
example1.php
展示了使用一个领域对象的命令和事件消息总线example2_event.php
展示了在命令中直接使用事件消息总线example3_sequential_commands.php
演示了命令是如何按顺序处理的tictactoe.php
使用CQRS实现了一个井字棋游戏SymfonyExample.md
展示了在Symfony2项目中实现的example1.php
设置
- 内存中命令处理程序,无事件发布/观察
<?php $userService = new UserService(); $commandBus = new DirectCommandBus() $commandBus->register('MyApp\ChangeEmailCommand', $userService);
- 内存中命令和事件处理程序
这使用LiteCQRS\EventProviderInterface
实例来触发领域事件。
<?php // 1. Setup the Library with InMemory Handlers $messageBus = new InMemoryEventMessageBus(); $identityMap = new SimpleIdentityMap(); $queue = new EventProviderQueue($identityMap); $commandBus = new DirectCommandBus(array( new EventMessageHandlerFactory($messageBus, $queue) )); // 2. Register a command service and an event handler $userService = new UserService($identityMap); $commandBus->register('MyApp\ChangeEmailCommand', $userService); $someEventHandler = new MyEventHandler(); $messageBus->register($someEventHandler);
- 内存中命令+自定义事件队列
LiteCQRS通过询问LiteCQRS\Bus\EventQueue
来了解触发的事件。提供您自己的实现,以使您的领域对象不必实现EventProviderInterface
即可独立。
<?php $messageBus = new InMemoryEventMessageBus(); $queue = new MyCustomEventQueue(); $commandBus = new DirectCommandBus(array( new EventMessageHandlerFactory($messageBus, $queue) ));
使用
实现您应用程序的用例
- 创建一个命令对象,接收所有必要的输入值。使用公共属性并扩展
LiteCQRS\DefaultCommand
以简化。 - 在您的任何服务(命令处理程序)中添加一个新方法,方法名为命令
- 将命令处理程序注册到命令总线,以处理给定的命令。
- 让您的实体实现
LiteCQRS\AggregateRoot
或LiteCQRS\DomainEventProvider
- 使用受保护的方法
raise(DomainEvent $event)
或apply(DomainEvent $event)
将事件附加到您的聚合根对象。
这就是简单用例的全部内容。
如果你的命令触发了监听器检查的事件,你应该
- 创建一个特定领域的事件类。使用公共属性来简化。
- 创建事件处理程序或向现有事件处理程序添加方法。
虽然为每个用例创建命令和事件看起来“复杂”,但这些对象实际上非常简单,仅包含公共属性。使用您的IDE或编辑器功能,您可以轻松地在短时间内生成它们。反过来,它们会使您的代码非常明确。
apply()和raise()的区别
有两种方式可以将事件发布到外部世界。
DomainEventProvider#raise(DomainEvent $event)
是一个简单的版本,它发出一个事件并什么都不做。AggregateRoot#apply(DomainEvent $event)
要求你添加一个方法apply$eventName($event)
,该方法可以用来在对象上重放事件。这用于从事件中重放对象。
如果你不使用事件溯源,那么你只需使用raise()
并完全忽略apply()
即可。
失败的事件
EventMessageBus阻止异常向上冒泡。为了允许对失败的事件处理程序执行进行一些调试,有一个特殊的事件“EventExecutionFailed”,您可以监听它。您将传递一个LiteCQRS\Bus\EventExecutionFailed
实例,其中包含$exception
、$service
和$event
属性,以便分析您应用程序中的失败。
扩展点
您应该实现自己的CommandBus
或扩展现有的以将整个过程连接在一起,使其完全符合您的工作需求。
插件
Symfony
在symfony中,您可以通过使用lite_cqrs.command_handler
或lite_cqrs.event_handler
标签来注册服务来使用LiteCQRS。这些服务随后将自动发现命令和事件。
命令和事件处理程序是从Symfony依赖注入容器中按需加载的。
要启用包,请将以下内容放入您的Kernel
new \LiteCQRS\Plugin\SymfonyBundle\LiteCQRSBundle(),
您可以通过添加以下内容到您的config.yml来启用/禁用包
lite_cqrs: ~
请参阅SymfonyExample.md文档,以了解如何在Symfony2项目中使用LiteCQRS的完整演示。
Monolog
一个插件,它使用Monolog记录每个命令和处理程序的执行情况。它包括消息的类型和名称、其参数作为json以及其执行成功或失败。
将Monolog集成到Symfony中会注册一个特定的通道lite_cqrs
,您可以从Symfony中的默认通道配置不同的通道。有关更多信息,请参阅Symfony食谱。