例如,假设我有三个聚合:订单,发票和发货.当客户下订单时,订单处理开始.但是,在发票已经支付并且货物已经准备好之前,不能发送货件.
>客户使用PlaceOrder命令下订单.
> OrderCommandHandler调用OrderRepository :: placeOrder().
> OrderRepository :: placeOrder()方法返回OrderPlaced事件,该事件存储在EventStore中并沿EventBus发送.
> OrderPlaced事件包含orderID并预先分配invoiceID和shipmentID.
> OrderProcess(“saga”)接收OrderPlaced事件,创建发票并在必要时准备发货(在事件处理程序中实现幂等).
6A.在某个时间点,OrderProcess接收InvoicePaID事件.它通过在ShipmentRepository中查找货件来检查货物是否已准备好,如果是,则发送货件.
6B.在某个时间点,OrderProcess接收ShipmentPrepared事件.通过在InvoiceRepository中查找发票来查看是否已支付发票,则发送货件.
对于那里所有经验丰富的DDD / CQRS / ES大师,你能告诉我我错过了什么概念以及为什么这个“无国籍传奇”的设计不起作用?
class OrderCommandHandler { public function handle(PlaceOrder $command) { $event = $this->orderRepository->placeOrder($command->orderID,$command->customerID,...); $this->eventStore->store($event); $this->eventBus->emit($event); }}class OrderRepository { public function placeOrder($orderID,$customerID,...) { $invoiceID = randomString(); $shipmentID = randomString(); return new OrderPlaced($orderID,$invoiceID,$shipmentID); }}class InvoiceRepository { public function createInvoice($invoiceID,...) { // Etc. return new InvoiceCreated($invoiceID,...); }}class ShipmentRepository { public function prepareShipment($shipmentID,...) { // Etc. return new ShipmentPrepared($shipmentID,...); }}class OrderProcess { public function onorderPlaced(OrderPlaced $event) { if (!$this->invoiceRepository->hasInvoice($event->invoiceID)) { $invoiceEvent = $this->invoiceRepository->createInvoice($event->invoiceID,$event->customerID,$event->invoiceID,...); $this->eventStore->store($invoiceEvent); $this->eventBus->emit($invoiceEvent); } if (!$this->shipmentRepository->hasShipment($event->shipmentID)) { $shipmentEvent = $this->shipmentRepository->prepareShipment($event->shipmentID,...); $this->eventStore->store($shipmentEvent); $this->eventBus->emit($shipmentEvent); } } public function onInvoicePaID(InvoicePaID $event) { $order = $this->orderRepository->getorders($event->orderID); $shipment = $this->shipmentRepository->getShipment($order->shipmentID); if ($shipment && $shipment->isPrepared()) { $this->sendShipment($shipment); } } public function onShipmentPrepared(ShipmentPrepared $event) { $order = $this->orderRepository->getorders($event->orderID); $invoice = $this->invoiceRepository->getInvoice($order->invoiceID); if ($invoice && $invoice->isPaID()) { $this->sendShipment($this->shipmentRepository->getShipment($order->shipmentID)); } } private function sendShipment(Shipment $shipment) { $shipmentEvent = $shipment->send(); $this->eventStore->store($shipmentEvent); $this->eventBus->emit($shipmentEvent); }}解决方法 命令可能会失败.
这是主要问题;我们首先聚合的全部原因是,它们可以保护业务免受无效状态变化的影响.那么如果createInvoice命令失败,onorderPlaced()会发生什么?
此外(尽管有些相关)你会迷失方向.流程经理处理事件;事件是过去已经发生的事情. Ergo – 流程经理过去一直在运行.在一个非常真实的意义上,他们甚至不能与任何已经看到比他们正在处理的事件更近的事件的人交谈(事实上,他们可能是第一个看到这个事件的处理者,这意味着其他人都是迈出过去).
这就是为什么你不能同步运行命令;你的事件处理程序是过去的,除非它在当前运行,否则聚合不能保护其不变量.您需要异步分派才能使命令针对正确的聚合版本运行.
下一个问题:当您异步调度命令时,无法直接观察结果.它可能会失败,或者在途中迷路,事件处理程序也不会知道.它可以确定命令成功的唯一方法是观察生成的事件.
结果是,进程管理器无法区分失败的命令和成功的命令(但事件尚未可见).为了支持有限的sla,你需要一个定时服务,不时唤醒流程管理器来检查事物.
当流程管理器唤醒时,需要状态才能知道它是否已完成工作.
有了国家,一切都变得如此简单易于管理.进程管理器可以重新发出可能丢失的命令,以确保它们通过,而不会使用已经成功的命令充斥域.您可以在不将时钟事件抛入域本身的情况下对时钟进行建模.
总结以上是内存溢出为你收集整理的域驱动设计 – 为什么sagas(又名流程管理器)包含内部状态,为什么它们会持久存储到事件存储中?全部内容,希望文章能够帮你解决域驱动设计 – 为什么sagas(又名流程管理器)包含内部状态,为什么它们会持久存储到事件存储中?所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)