Domain driven design 域事件与事件源和CQR的因果关系

Domain driven design 域事件与事件源和CQR的因果关系,domain-driven-design,cqrs,event-sourcing,causality,akka-persistence,Domain Driven Design,Cqrs,Event Sourcing,Causality,Akka Persistence,假设我们有一个写入模型(域),它生成两个事件: 载体添加(…) 总线连接已创建(运营商,…) 载体和母线连接类别是(部分)单独的集合。总线连接被分配给一个载体并包含其载体id(单独的聚合仅由id引用) 在正常的命令和事件流中,写模型和读模型中的一切都很好,但是当我们想要从头开始重建/添加新的读模型时,问题就会出现 许多人建议(例如akka持久性库)事件存储在事件存储中的每个聚合中。当反规范化程序要求回复事件时,他从每个聚合中获得两个独立的事件流。问题是,来自不同聚合的一些事件(如上面的示例)

假设我们有一个写入模型(域),它生成两个事件:

  • 载体添加(…)
  • 总线连接已创建(运营商,…)
载体和母线连接类别是(部分)单独的集合。总线连接被分配给一个载体并包含其载体id(单独的聚合仅由id引用)

在正常的命令和事件流中,写模型和读模型中的一切都很好,但是当我们想要从头开始重建/添加新的读模型时,问题就会出现

许多人建议(例如akka持久性库)事件存储在事件存储中的每个聚合中。当反规范化程序要求回复事件时,他从每个聚合中获得两个独立的事件流。问题是,来自不同聚合的一些事件(如上面的示例)需要按照添加到事件存储中的相同顺序进行响应。这意味着我们需要某种因果依赖/偏序

最后,我的问题是:

  • 我是否应该重新思考我的领域设计(糟糕的聚合边界?)
  • 我是否只需要强制执行偏序
如果是后者,最有效的方法是什么

  • 全球计数器?似乎没有可扩展性
  • 某种向量钟
  • 当这些问题出现时,是否在反规范化器中检测它们?例如,我们获得了CarrierId,但还没有具有此id的CarrierAdded事件,因此我们将该事件隐藏起来,并首先等待预期的事件
  • 在重播模式下处理事件时引入一些顺序?例如,首先是与运营商有关的所有事件,然后是与总线连接相关的事件

不,在我看来,您的设计非常精致,非常普通。你必须以某种方式强制偏序

我不熟悉akka持久性,但一些事件存储通过记录事件的时间戳实现偏序,并按照事件的时间戳顺序重播事件。某些事件存储确实使用全局计数器,例如,当底层数据库是关系型的,并且系统不需要扩展时——数据库提供的序列号在这里工作正常

时间戳当然只能保证在给定粒度(通常为1ms)下进行排序。在某些情况下,这已经足够了,例如,如果您可以确保CarrierAdded不会在与BusCarrierAdded相同的毫秒内发生

至于可伸缩性,请确保扩展确实是一个问题,在您的情况下,公共汽车交通系统似乎不是“大规模的”。。。如果您可以使用单个主数据库服务器,那么事件流中的事件序列号是实现所需偏序的直接可靠的方法。序列号在“交织”/“并行提交”时可能会失败,因此您需要确保在BusCarrierAdded之前始终将CarrierAdded事件添加到事件存储/数据库中,但在您的场景中可能会出现这种情况


如果您确实需要向外扩展,并且两个事件可能在同一毫秒内出现,那么您确实需要在处理程序中检测问题并使用非规范化。这听起来比实际困难,因为它可以很容易地用一个非常简单的状态机实现。乔纳森·奥利弗(Jonathan Oliver)在这部传奇故事中有过类似的经历,但这一原则也适用于此。

为了打破紧密的联系,关于事件和命令的“因果关系计数器”怎么样?本质上,这是一种在副作用树中衡量命令/事件“深度”的方法,其根源是在第0步启动级联的任何外部/计划/用户触发命令。当您有两个具有相同时间戳的项时,通常可以先处理具有较低计数器的项,即使它们实际上彼此没有关联。是的,这也可以工作(但我认为这可能很难实现或给开发人员带来一些负担)。