3

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

  • 运营商添加(...)
  • BusConnectionCreated(运营商,...)

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

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

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

最后是我的问题:

  • 我应该重新考虑我的域设计(错误的聚合边界?)还是
  • 我只需要强制执行部分排序吗?

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

  • 全球柜台?似乎没有可扩展性。
  • 某种矢量时钟?
  • 当它们出现时在反规范化器中检测到这些问题?例如,我们得到了 CarrierId,我们还没有具有此 ID 的 CarrierAdded 事件,所以我们将事件存储起来并首先等待预期的事件
  • 在重放模式中处理事件方面引入一些顺序?例如,首先与运营商有关的所有事件,然后是与 BusConnection 相关的事件?
4

1 回答 1

6

不,IMO 你的设计非常好而且很常见。您必须以某种方式强制执行部分排序。

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

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

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

如果您确实需要横向扩展并且两个事件可能出现在同一毫秒内,您确实必须在处理程序中检测问题并使用非规范化。这听起来比实际上更难,因为它可以通过一个非常简单的状态机轻松实现。Jonathan Oliver 曾在 sagas 的背景下写过关于状态匹配的博客,但该原则也适用于此处。

于 2014-05-20T07:24:44.877 回答