floriansemm/lite-cqrs

简单的命令-查询-责任分离库。

v1.1-alpha2 2013-03-02 07:07 UTC

This package is auto-updated.

Last update: 2024-09-08 00:09:35 UTC


README

基于命名约定的PHP CQRS库(部分基于C#的LiteCQRS),依赖消息总线、命令、事件溯源和领域事件模式。

Build Status (Master)

注意 使用1.1分支,因为dev-master目前正在进行大量重构。

术语

CQS是命令-查询分离:一种范式,其中读取方法永远不会更改状态,写入方法永远不会返回数据。CQRS在此基础上建议将读取模型与写入模型分离,并使用领域事件模式来通知读取模型写入模型中的更改。

LiteCQRS使用命令模式和中央消息总线服务,该服务找到执行命令的相应处理程序。命令只是一个具有一些属性的类,它可以选择实现LiteCQRS\Command

在命令执行过程中,可以触发领域事件。这些事件也是具有一些属性的简单类,它们可以选择实现LiteCQRS\DomainEvent

事件队列知道在命令执行期间触发了哪些领域事件,然后将其发布到事件消息总线,许多监听器可以监听它们。

更改

从1.0到1.1

  • 不再需要扩展LiteCQRS\CommandLiteCQRS\DomainEvent。实际上,您可以使用任何类作为命令或事件。仅命名约定本身就可以确保检测命令处理程序和事件监听器。

  • JMS Serializer插件无法“分离”事件中的聚合根属性,这些属性是事件序列化的一部分。因此,不再支持将相关的聚合根放入事件(即使使用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堆栈中命令通常采取的步骤

  1. 将命令推入 CommandBus。命令是由您创建的扩展 Command 的简单对象。
  2. CommandBus 检查是否有可以执行您的命令的处理程序。每个命令恰好有一个处理程序。
  3. 命令处理程序改变领域模型的状态。它通过创建事件(表示状态变化)并将它们传递给您的领域对象的 AggregateRoot::apply()DomainEventProvider::raise() 方法来实现。
  4. 当命令完成时,命令总线将检查身份映射中的所有对象以查找事件。
  5. 所有找到的事件都将传递给 EventMessageBus#publish() 方法。
  6. EventMessageBus 将所有事件分发给观察事件处理程序。
  7. 事件处理程序可以使用 CommandBus 再次创建新的命令。

命令和事件处理程序执行可以包装在管理事务的处理程序中。事件处理始终在命令事务之外触发。如果命令失败并抛出任何异常,则所有由命令创建的事件都将被忽略。在这种情况下,不会触发任何事件处理程序。

对于内存中的命令总线(InMemory CommandBus)和事件消息总线(EventMessageBus),LiteCQRS 确保命令和事件处理程序的执行永远不会嵌套,而是按照线性顺序执行。这防止了独立事务对每个命令的影响。

示例

有关示例,请参阅 examples/

  1. example1.php 展示了一个领域对象与命令和事件消息总线的使用
  2. example2_event.php 展示了在命令中直接使用事件消息总线
  3. example3_sequential_commands.php 证明了命令是如何按顺序处理的
  4. tictactoe.php 使用 CQRS 实现了井字棋游戏
  5. SymfonyExample.md 展示了在 Symfony2 项目范围内实现的 example1.php

设置

  1. 内存命令处理程序,无事件发布/观察
<?php
$userService = new UserService();

$commandBus = new DirectCommandBus()
$commandBus->register('MyApp\ChangeEmailCommand', $userService);
  1. 内存命令和事件处理程序

这使用 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);
  1. 内存命令 + 自定义事件队列

LiteCQRS 通过询问 LiteCQRS\Bus\EventQueue 来了解触发的事件。提供您自己的实现,以便您的领域对象不需要实现 EventProviderInterface

<?php
$messageBus = new InMemoryEventMessageBus();
$queue = new MyCustomEventQueue();

$commandBus = new DirectCommandBus(array(
    new EventMessageHandlerFactory($messageBus, $queue)
));

使用

要实现应用程序的用例

  1. 创建一个接收所有必要输入值的命令对象。使用公共属性并扩展 LiteCQRS\DefaultCommand 以简化。
  2. 在您的任何服务中添加一个名为命令的方法(命令处理程序)
  3. 将命令处理程序注册到命令总线,以处理给定的命令。
  4. 让您的实体实现 LiteCQRS\AggregateRootLiteCQRS\DomainEventProvider
  5. 使用受保护的 raise(DomainEvent $event)apply(DomainEvent $event) 方法将事件附加到您的聚合根对象。

这就是简单用例的全部内容。

如果您的命令触发监听器检查的事件,您应该

  1. 创建一个特定领域的事件类。使用公共属性以简化。
  2. 创建事件处理程序或向现有事件处理程序添加方法。

虽然为每个用例创建命令和事件似乎很复杂,但这些对象非常简单,只包含公共属性。使用您的 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_handlerlite_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 烹饪书