简单的[C]ommand [Q]uery [R]esponsibility [S]egregation和[E]vent [S]ourcing库。

维护者

详细信息

github.com/LidskaSila/Glow

源代码

安装: 3,411

依赖项: 0

建议者: 0

安全: 0

星标: 2

关注者: 9

分支: 47

v0.9 2017-08-28 12:31 UTC

This package is not auto-updated.

Last update: 2024-09-15 01:43:43 UTC


README

这个库旨在展示另一种处理CQRS和事件源的方法,但并不认为它适合用于实际的production项目。为此,更建议使用Prooph工具箱Nette扩展Prooph工具箱。它支持快照、读取模型投影,易于进行事件回放,并且更加灵活。

这个PHP CQRS EventSourcing库基于Benjamin Eberlei的LiteCQRS for php,该库长时间没有维护。这个分支使其重新焕发生机,但它不应被视为稳定的库,因为未来可能会出现BC breaks。

Build Status Quality Score Code Coverage Downloads this Month

主要区别在于

  • PHP的最低要求版本是7.0,很快将升级到7.1。
  • 命令 - 只可以向CommandBus注册ComandHandler(只有handle方法)的实现。这样做的原因是为了强制执行命名和结构约定。当你看到XxxCommand并需要看到实现时,你知道应该在某个地方实现XxxCommandHandler。

原始更新的README

基于小命名约定的PHP CQRS库(松散基于LiteCQRS for C#),它依赖于MessageBus、Command、EventSourcing和Domain Event模式。

术语

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

Glow使用命令模式和中央消息总线服务,该服务查找相应的处理器来执行命令。命令必须实现Glow\Commanding\Command。它应该是一个简单的DTO(数据传输对象),仅包含一些描述它的属性(鼓励不可变性)。

在这个对象作为参数传递给CommandBus的handle(Command $command)方法后,CommandBus找到适当的CommandHandler,并在其上调用相同的方法和相同的参数。强制约定是,对于每个XxxCommand,必须存在XxxCommandHandler(实现CommandHandler)。

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

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

更改

约定

  • 每个XxxCommand DTO在XxxCommand实现Command且XxxCommandHandler实现CommandHandler时映射到XxxCommandHandler。
  • 领域事件应用于事件处理器“Event Class Shortname”=>“onEventClassShortname”。只有匹配时才会注册事件监听器。
  • 领域事件应用于实体/聚合根“Event Class Shortname”=>“applyEventClassShortname”
  • 你可以选择扩展DefaultDomainEvent,它有一个构造函数,将数组输入映射到属性,并在传递未知属性时抛出异常。
  • 还有一个与DefaultDomainEvent具有相同语义的DefaultCommand。扩展这个不是必需的。

示例

  • GreetingCommand implements Command映射到注册的实现了CommandHandler的GreetingCommandHandler上的handle(GreetingCommand $command)方法。
  • HelloWorld\GreetedEvent被传递到所有具有方法onGreeted(GreetedEvent $event)的事件处理器。
  • HelloWorld\Events\Greeted被传递到所有具有方法onGreeted(Greeted $event)的事件处理器。
  • 当在聚合根上创建时,HelloWorld\GreetedEvent会被委托到applyGreeted($event)

安装 & 要求

使用Composer安装

composer require lidskasila/glow

工作流程

以下是在执行过程中命令通常通过Glow堆栈采取的步骤

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

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

对于内存中的命令总线和企业消息总线,Glow确保命令和事件处理器的执行永远不会嵌套,而是按顺序线性化。这防止了每个命令的独立事务相互影响。

#TODO以下未更新

示例

请参阅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. 内存中的命令和事件处理器

这使用Glow\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. 内存中的命令 + 自定义事件队列

Glow通过询问Glow\Bus\EventQueue来了解触发的事件。提供自己的实现以独立于领域对象实现EventProviderInterface

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

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

使用

实现您应用程序的用例

  1. 创建一个命令对象,接收所有必要的输入值。使用公共属性并扩展Glow\DefaultCommand以简化。
  2. 在您的任何服务(命令处理器)中添加一个新方法,其名称与命令相同
  3. 将命令处理器注册到命令总线以处理给定的命令。
  4. 让您的实体实现Glow\AggregateRootGlow\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”,你可以监听它。你将传递一个包含属性 $exception$service$eventGlow\Bus\EventExecutionFailed 实例,以便分析应用程序中的失败。

扩展点

你应该实现自己的 CommandBus 或扩展现有以将整个过程连接起来,使其完全符合你的工作需要。

插件

Symfony

在symfony内部,你可以通过注册带有 lite_cqrs.command_handlerlite_cqrs.event_handler 标签的服务来使用Glow。然后,这些服务将自动发现用于命令和事件。

命令和事件处理器是从Symfony依赖注入容器中懒加载的。

要启用包,请将以下内容放入你的Kernel中

new \Glow\Plugin\SymfonyBundle\GlowBundle(),

你可以通过添加以下内容到你的config.yml中来启用/禁用包

lite_cqrs: ~

请参阅 SymfonyExample.md 文档,以了解如何在Symfony2项目中使用Glow的完整演示。

Monolog

一个插件,使用 Monolog 记录每个命令和处理器的执行。它包括消息的类型和名称、其参数作为json以及其执行是否成功或失败。

Monolog集成到Symfony中注册了一个特定的通道 lite_cqrs,你可以配置它以与Symfony中的默认通道不同。有关更多信息,请参阅 Symfony食谱