事件采购重构

Rod*_*eal 5 domain-driven-design cqrs event-sourcing

我一直在研究DDD,偶然发现了CQRS和事件采购(ES)等设计模式.这些模式可用于帮助实现DDD的一些概念,而不需要花费太多精力.在下面举例说明的体系结构中,聚合知道如何处理与其自身相关的命令和事件.换句话说,事件处理程序和命令处理程序是聚合.

然后,我开始建模一个示例域,只是为了理解实现如何遵循业务逻辑.对于这个问题,这里是我的域名(基于):

示例域

我知道这是一个错误的建模示例,但我只是作为一个例子使用它.因此,使用ES,在操作结束时,我们会将所有事件(绿色箭头)保存到事件存储中(如果没有异常),将每个事件保存到其给定的事件流(聚合类型+聚合ID)中:

事件流

一切似乎都是正确的.因此,如果我们想要重建任何此Aggregate实例的内部状态,我们只需要新建它(new())并以正确的顺序应用保存在各自事件流中的所有事件.

事件重建

我的问题与模型的变化有关.因为,软件开发是一个我们永远不会停止了解我们的领域的过程,我们总是带来新的想法.那么,让我们分析一些变化情景:

更改场景1:

我们假装现在,如果预订聚合检查座位不可用,它应该发送一个事件(座位未保留),这个事件应由一个新的聚合处理,该聚合将存储所有未获得座位的人:

第一次改变

在旧系统已正确处理初始命令(放置顺序)并将所有事件保存到其各自事件流的假设中:

  • 当我们想要重建任何此Aggregate实例的内部状态时,我们只需要新建它(new())并以正确的顺序应用保存在各自事件流中的所有事件.(没有改变).唯一的问题是,新的用例在旧模型中不存在.

更改场景2:

让我们假装现在,当付款被接受时,我们将在新的Aggregate(财务汇总)中处理此事件(已接受付款),而不再在订单汇总中处理.它会向订单总计发送一个新事件(已收到付款).我知道这种情况结构不合理,但这种情况可能会发生.

第二次改变

在旧系统已正确处理初始命令(放置顺序)并将所有事件保存到其各自事件流的假设中:

  • 当我们想要重建任何此Aggregate的实例的内部状态时,我们在将聚合事件流中的事件应用于自身时遇到问题:

错误重建

现在,订单不再知道如何处理付款接受事件.

问题

因此,如示例所示,每当系统更改反映在由不同事件处理程序(Aggregate)处理的事件中时,存在一些主要问题.因为,我们不能再重建内部状态了.所以,这个问题可以有一些解决方案:

可能解决方案

当事件未由存储事件流的聚合处理时,我们可以找到新处理程序并创建新实例并将事件发送给它.但是为了保持内部状态正确,我们需要最后一个事件(Payment Received)由Order Aggregate处理.所以,我们让它调度事件(以及可能的命令):

解决方案重建

该解决方案可能存在一些问题.让我们假设一个新命令(Place Order)到了,它必须创建这个订单实例并保存新状态.现在我们有:

新流

灰色是当系统尚未完成模型更改时已在上次调用中保存的事件.我们可以看到为新聚合(Finance W)创建了一个新的事件流.我们可以看到Event Streams仅附加,因此订单Y事件流中的付款接受事件仍然存在.Finance W Event Stream中的第一个Payment Accepted事件是应该由Order处理但必须找到新处理程序的事件.订单的事件流中的黄色付款收到事件是当订单的事件流中的付款已接受事件由财务处理时由接受付款的新处理程序生成事件.所有其他绿色事件都是通过在新模型中处理下订单命令生成的新事件.

解决方案有问题

下次需要重建聚合时,流中会有一个支付接受事件(因为它只是追加),它会再次调用新的处理程序,但这已经完成,并且付款接收事件已经完成已经保存到流中.所以,没有必要再次通过这个,我们可以忽略这个事件并继续.

所以,我的问题是我们如何处理影响谁处理每个事件的模型更改?在这样的更改之后,我们如何重建Aggregate的内部状态?我们是否需要构建一些事件流迁移,将事件从一个流更改为新模式(一个或多个流)?就像我们在关系数据库中需要的那样?我们永远不会被允许删除一个处理程序,所以我们只能添加新的处理程序吗?这将导致无法管理的系统......

Con*_*enu 5

你几乎没问题,除了一件事:聚合不应该处理来自其他聚合的事件。这就像一个非事件源聚合与另一个聚合共享一个表:他们不应该。

在事件驱动的 DDD 中,聚合是系统的构建块,用于接收命令(表达意图的事物)并返回事件(已发生的事物)。对于每个命令类型,必须存在一种且只有一种处理它的聚合类型。在执行命令之前,聚合被提供给它自己之前发出的所有事件,也就是说,这个聚合实例过去发出的每个事件都按时间顺序应用于这个聚合实例。

因此,如果您想对系统进行正确建模,则不允许将来自一个聚合的事件作为事件发送到另一个聚合(不同类型或实例)。

如果您需要对涉及多个聚合的业务流程进行建模,正确的做法是使用Saga/Process manager。这是一个不同的组件。它与聚合相反。它接收聚合发出的事件并将命令发送到其他聚合。

在最简单的情况下,Saga 管理器只是从一个 Event 中获取属性并使用这些属性创建并填充一个 Command。然后它将命令发送到目标聚合。

在更复杂的情况下,Saga 等待多个事件,当所有事件都收到时,它才会创建并发送一个命令。

Saga 还可以对事件进行重复数据删除或重新排序。

在您的情况下,Saga 可能是Sale,其目的是协调从订购到产品调度的整个销售流程。

总之,你有这个问题,因为你没有正确地建模你的系统。如果你的聚合只处理它们的特定命令(而不是其他人的事件),那么即使你必须在新业务流程出现时创建一个新的 Saga,它也会将相同的命令发送到相同的聚合。


Voi*_*son 2

简要回答

我的问题是我们如何处理影响谁处理每个事件的模型更改?

处理事件通常很容易改变,因为处理部分是短暂的。事件只有一个作者,但可以有多个读者。您只需要安排管道将事件通知给每个订阅者即可。

因此,在场景 #1 中,PaymentAggregate 记录 PaymentAccepted 事件(在其自己的流中),然后您的管道通知 OrderAggregate PaymentAccepted 事件发生,并以自己的逻辑执行下一步操作。

要更改为场景 #2,我们将保持 Payment Aggregate 不变,但我们将安排管道,以便它向 FinanceAggregate 告知 PaymentAccepted,并告知 OrderAggregate 关于 PaymentReceived。

你的照片让人很难看出这一点;我认为您没有仔细跟踪状态的每个更改都存储在更改的聚合流中。不是你的错——微软的形象真的很糟糕。

换句话说,您的箭头#3“保留座位”不是一个SeatsReserved事件,而是一个Handle(SeatsReserved) 命令