【问题标题】:Domain events causal dependency with Event Sourcing and CQRS使用事件溯源和 CQRS 的领域事件因果关系
【发布时间】:2014-07-08 02:05:03
【问题描述】:

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

  • CarrierAdded(...)
  • BusConnectionCreated(carrier, ...)

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

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

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

最后是我的问题:

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

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

  • 全局计数器?似乎不可扩展。
  • 某种矢量时钟?
  • 在反规范化器中检测到此类问题何时出现?例如。我们得到了 CarrierId,但我们还没有具有此 ID 的 CarrierAdded 事件,因此我们将事件存储起来并首先等待预期的事件
  • 在重放模式中处理事件方面引入一些顺序?例如。所有与 Carriers 相关的事件,然后是 BusConnection 相关事件?

【问题讨论】:

    标签: domain-driven-design cqrs event-sourcing causality akka-persistence


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 为了打破亲密关系,事件和命令的“因果计数器”怎么样?本质上,一种衡量副作用树中命令/事件“深度”的方法,根植于在步骤零开始级联的任何外部/计划/用户触发命令。当您有两个具有相同时间戳的项目时,通常首先处理具有较低计数器的项目是安全的,即使它们实际上并不相互关联。
    • 是的,这也可以工作(但我想它可能难以实施或给开发人员带来一些负担)。
    猜你喜欢
    • 2021-07-25
    • 2019-01-31
    • 1970-01-01
    • 2018-11-15
    • 2019-09-22
    • 2018-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多