PHP号称是最好的服务器端编程语言,CQRS + Event Sourcing已经在PHP社区和.NET社区蔓延开来,而Java社区由于Spring和JavaEE的垄断相对要落后些,废话少说,看看这款PHP库包dudulina的特点:
1.领域模型代码最小化依赖该库包:
只需要实现三个接口,没有extends继承(接口实现高于类继承),这样你的领域代码就会保持干净,对自己运行在哪种基础架构或框架中就会毫不知情。
(1)\Dudulina\Event :每个领域事件实现这个接口; 它没有方法,它只是一个标记接口; 领域事件会被自动代码生成工具检测到;
(2)\Dudulina\Command:每个领域命令会实现这个接口; 它只有一种方法getAggregateId(); 命令调度器需要根据这个ID从Repository加载对应的Aggregate实例
(3)\Dudulina\ReadModel\ReadModelInterface:这是每个读模型需要实现的; 这仅在使用ReadModelRecreator重建读模型(预测)时才是必需的。
通过实现少数几个接口可以更加与dudulina松散耦合。可以定义和使用自己的领域接口,这个接口可以继承dudulina库包。这样,当您更改库时,只需更新这些接口就可以了。
2. 写入端的最小代码重复
在写入方面,您只需要实例化一个命令并将其发送给CommandDispatcher;
我们来创建一个命令:
// immutable and Plain PHP Object (Value Object) // No inheritance! class DoSomethingImportantCommand implements Command { private $idOfTheAggregate; private $someDataInTheCommand;
public function __construct($idOfTheAggregate, $someDataInTheCommand) { $this->idOfTheAggregate = $this->idOfTheAggregate; $this->someDataInTheCommand = $this->someDataInTheCommand; }
public function getAggregateId() { return $this->idOfTheAggregate; }
public function getSomeDataInTheCommand() { return $this->someDataInTheCommand; } }
|
创建一个简单事件:
// immutable, simple object, no inheritance, minimum dependency class SomethingImportantHappened implements Event { public function __construct($someDataInTheEvent) { $this->someDataInTheEvent = $someDataInTheEvent; }
public function getSomeDataInTheEvent() { return $this->someDataInTheEvent; } }
|
下面是UI代码:
class SomeHttpAction { public function getDoSomethingImportant(RequestInterface $request) { $idOfTheAggregate = $request->getParsedBody()['id']; $someDataInTheCommand = $request->getParsedBody()['data'];
$this->commandDispatcher->dispatchCommand(new DoSomethingImportantCommand( $idOfTheAggregate, $someDataInTheCommand ));
return new JsonResponse([ 'success' => 1, ]); } }
|
只需要实现命令和事件,以及必要的UI,其他什么也不用做,就是这样,没有事务管理,不需要从存储库加载,什么也没有。命令作为一个参数传入聚合的命令处理器,如下所示:
class OurAggregate { //.... public function handleDoSomethingImportant(DoSomethingImportantCommand $command) { if($this->ourStateDoesNotPermitThis()){ throw new \Exception("No no, it is not possible!"); }
yield new SomethingImportantHappened($command->getSomeDataInTheCommand()); }
public function applySomethingImportantHappened(SomethingImportantHappened $event, Metadata $metadata) { //Metadata is optional $this->someNewState = $event->someDataInTheEvent; } }
|
读取模型将接收到上面命令处理器引发的事件,并在保存事件之后处理事件,看一看可读模型:
class SomeReadModel { //...some database initialization, i.e. a MongoDB database injected in the constructor
public function onSomethingImportantHappened(SomethingImportantHappened $event, Metadata $metadata) { $this->database->getCollection('ourReadModel')->insertOne([ '_id' => $metadata->getAggregateId() 'someData' => $event->getSomeDataInTheEvent() ]); }
//this method could be used by the UI to display the data public function getSomeDataById($id) { $document = $this->database->getCollection('ourReadModel')->findOne([ '_id' => $metadata->getAggregateId() ]);
return $document ? $document['someData'] : null; } }
|
读模型在另外一个单独进程更新专门的读数据库,近似实时(通过tailing),或从事件存储中轮询甚至可以使用Javascript。当前端命令到达被分派处理时时,会发生以下过程:
1. 根据Id定位到聚合实例
2. 从数据库库加载 聚合,重放所有以前的事件
3. 该命令被分派到对应聚合实例
4. 聚合产生事件
5. 事件写入保存到专门的事件数据库
6. 读模型会收到新的写入事件通知,并更新专门的读数据库
7. Safa也会被通知; 如果saga(事务管理器)产生其他命令,则循环再次开始。
GitHub - xprt64/dudulina: CQRS + Event Sourcing li