域事件与事件源和CQRS的因果依赖性

Meq*_*rel 3 domain-driven-design causality cqrs event-sourcing akka-persistence

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

  • CarrierAdded(...)
  • BusConnectionCreated(运营商,...)

Carrier和BusConnection类是(部分)单独的聚合.BusConnection被分配给Carrier并包含其CarrierId(单独的聚合仅由id引用).

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

许多人建议(例如akka-persistence库)事件存储在事件存储中的每个聚合中.当反规范器要求回复事件时,他从每个聚合中获得两个独立的事件流.问题是来自上述示例中的不同聚合的某些事件需要按照它们添加到事件存储的相同顺序进行回复.这意味着我们需要某种因果依赖/部分排序.

最后我的问题:

  • 我应该重新考虑我的域名设计(糟糕的聚合边界?)或
  • 我是否只需要执行部分订购?

如果是后者,那么最有效的方法是什么?

  • 全球专柜?似乎不是可扩展的.
  • 某种矢量时钟?
  • 它们出现时会在非规范化程序中检测到这些问题吗?例如,我们得到了CarrierId,我们还没有带有此ID的CarrierAdded事件,所以我们先存储事件并等待预期的事件
  • 在重放模式下处理事件时引入一些顺序?例如,有关运营商的所有事件首先是BusConnection相关事件吗?

Ale*_*ger 6

不,IMO你的设计非常精致且很常见.你必须以某种方式强制执行部分排序.

我不熟悉akka-persistence,但是一些事件存储通过记录事件的时间戳来实现部分排序,并按照时间戳的顺序重放事件.一些事件存储确实使用全局计数器,例如当底层数据库是关系数据库且系统不需要扩展时 - 数据库提供的序列号在这里工作正常.

时间戳当然只能保证订购达到给定的粒度,通常为1ms.在某些情况下,这已经足够了,例如,如果您可以确保CarrierAdded 永远不会在与BusCarrierAdded相同的毫秒内发生.

至于可扩展性,确保横向扩展确实是一个问题,公共汽车运输系统似乎并不"大规模"......如果您可以使用单个主数据库服务器,事件中的事件序列数量流是实现所需部分排序的直接且可靠的方式."交织"/并行提交的序列号可能会失败,因此您需要确保在BusCarrierAdded之前始终将CarrierAdded事件添加到事件存储/数据库,但这似乎可能在您的方案中.

如果确实需要向外扩展并且两个事件都可能出现在同一毫秒内,那么您确实必须在处理程序中检测问题并使用非规范化.这听起来比它更难,因为它可以通过一个非常简单的状态机轻松实现.乔纳森·奥利弗(Jonathan Oliver)传奇语言中发表过关于州匹配的博客,但这个原则也适用于此.