gpt4 book ai didi

domain-driven-design - 为什么传奇(又名流程管理器)包含内部状态,为什么它们会持久化到事件存储中?

转载 作者:行者123 更新时间:2023-12-04 18:36:17 25 4
gpt4 key购买 nike

很多关于 CQRS 的文章都暗示 sagas 有一个内部状态,必须保存到事件存储中。我不明白为什么这是必要的。

例如,假设我有三个聚合:Order , InvoiceShipment .当客户下订单时,订单流程开始。但是,必须先支付发票并先准备好货件,然后才能发送货件。

  • 客户通过 PlaceOrder 下订单命令。
  • OrderCommandHandler电话OrderRepository::placeOrder() .
  • OrderRepository::placeOrder()方法返回 OrderPlaced事件,存储在 EventStore并发送 EventBus .
  • OrderPlaced事件包含 orderId并预分配一个 invoiceIdshipmentId .
  • 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, $customerId, $invoiceId, $shipmentId);
    }
    }

    class InvoiceRepository {
    public function createInvoice($invoiceId, $customerId, ...) {
    // Etc.
    return new InvoiceCreated($invoiceId, $customerId, ...);
    }
    }

    class ShipmentRepository {
    public function prepareShipment($shipmentId, $customerId, ...) {
    // Etc.
    return new ShipmentPrepared($shipmentId, $customerId, ...);
    }
    }

    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, $event->customerId, ...);
    $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,您需要一个定时服务来不时唤醒进程管理器以检查事物。

    当进程管理器醒来时,它需要状态来知道它是否已经完成了工作。

    有了状态,一切都变得更容易管理。进程管理器可以重新发出可能丢失的命令以确保它们通过,而不会用已经成功的命令淹没域。您可以对时钟进行建模,而无需将时钟事件放入域本身。

    关于domain-driven-design - 为什么传奇(又名流程管理器)包含内部状态,为什么它们会持久化到事件存储中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34304507/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com