小编Rod*_*eal的帖子

与 DDD + CRQS + ES 的并发

我研究 DDD 一段时间了,并偶然发现了 CQRS 和事件溯源 (ES) 等设计模式。这些模式可用于帮助以更少的努力实现 DDD 的某些概念。

\n\n

然后我开始开发一个简单的软件来实现所有这些概念。并开始想象可能的失败路径。

\n\n

为了阐明我的架构,下图描述了来自前端并到达后端控制器的一个请求(为简单起见,我忽略了所有过滤器、绑定器)。

\n\n

时序图

\n\n
    \n
  1. 演员发送一张表格,其中包含他想从一个账户提取的金额。
  2. \n
  3. 控制器将视图模型传递到应用层,在应用层将其转换为一个命令
  4. \n
  5. 应用层打开一个工作单元(UOW)将VM映射到命令并将命令发送到调度程序。
  6. \n
  7. 调度程序找到知道如何处理命令(帐户)的相应聚合类,并向工厂请求帐户的特定实例。
  8. \n
  9. 工厂创建一个新的帐户实例并从事件存储中请求所有事件。
  10. \n
  11. 事件存储返回帐户的所有事件。
  12. \n
  13. 工厂将所有事件发送到聚合,以便其内部状态正确。并返回帐户的实例。
  14. \n
  15. 调度程序将命令发送到帐户,以便可以对其进行处理。
  16. \n
  17. 帐户检查是否有足够的资金进行提款。如果有,它会发送一个新事件“MoneyWithdrawnEvent”。
  18. \n
  19. 此事件由更改其内部状态的聚合(帐户)处理。
  20. \n
  21. 应用程序层关闭 UOW,当关闭时,UOW 检查所有加载的聚合,以检查它们是否有新事件要保存到事件存储中。如果有,它将事件发送到存储库。
  22. \n
  23. 存储库将事件保存到事件存储中。
  24. \n
\n\n

可以添加很多层,例如:聚合缓存、事件缓存、快照等。

\n\n

有时ES可以与关系数据库并行使用。这样,当 UOW 保存已发生的新事件时,它还会将聚合持久保存到关系数据库中。

\n\n

ES 的好处之一是它拥有一个中心事实来源,即事件存储。因此,即使内存中甚至关系数据库中的模型被损坏,我们也可以从事件中重建模型。

\n\n

有了这个事实来源,我们就可以构建其他系统,这些系统可以以不同的方式使用事件来形成不同的模型。

\n\n

然而,要实现这一点,我们需要真相来源干净且未被损坏。否则所有这些好处都不会存在。

\n\n

也就是说,如果我们考虑图中描述的架构中的并发性,可能会出现一些问题:

\n\n
    \n
  • 如果actor在一个排序周期内向后端发送两次表单,并且后端启动两个线程(每个请求一个),那么它们将调用两次应用层,并启动两个UOW,依此类推。这可能会导致两个事件存储在事件存储中。
  • \n
\n\n

这个问题可以在很多不同的地方处理:

\n\n
    \n
  1. 前端可以控制哪个用户/演员可以执行什么操作以及执行多少次。

  2. \n
  3. 调度程序可以拥有正在处理的所有命令的一个缓存,如果存在引用同一聚合(帐户)的命令,则会抛出异常。

  4. \n
  5. 存储库可以创建聚合的新实例,并在保存之前运行事件存储中的所有事件,以检查版本是否仍与步骤 7 中获取的版本相同。

  6. \n
\n\n

每个解决方案的问题:

\n\n
    \n
  1. 前端

    \n\n
      \n …

architecture domain-driven-design cqrs event-sourcing dddd

5
推荐指数
1
解决办法
2395
查看次数

事件采购重构

我一直在研究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处理但必须找到新处理程序的事件.订单的事件流中的黄色付款收到事件是当订单的事件流中的付款已接受事件由财务处理时由接受付款的新处理程序生成 …

domain-driven-design cqrs event-sourcing

5
推荐指数
2
解决办法
543
查看次数