什么时候事件溯源在命令端有用

When is event sourcing useful on command side

为了自学一些 DDD,我正在阅读 Vlad Khononov 的书 Learning Domiain-Driven Design,并且我来到了 CQRS 与事件溯源的部分。 过去,我见过 DDD 的多个示例,例如参见弗拉基米尔·霍里科夫 DDD in Practice repository。链接页面显示了一个聚合,其方法都以一个域事件结束,该域事件被附加到域事件列表,随后可以使用消息队列将其发布给任何感兴趣的各方。

这种方法的优点在于,如果在某个阶段必须创建新视图(查询端),则可以简单地重播所有这些事件并构建新视图。我在这里假设消息队列无限期地存储所有事件,或者订阅者将所有这些事件存档以便以后可以重播。

引入事件源时,这些事件不再需要存档,因为它们存储在事件存储中。这是有代价的,保存和再水化聚集体变得更加麻烦。那么能够在命令端重播事件有什么好处呢?是否存在在命令端重播事件比在查询端重放事件更有利的场景?

编辑: 只需阅读可调试性可能是一个原因。例如,如果聚合包含无效数据,您可以简单地逐个重放事件并找出问题出在哪里。我怀疑额外调试功能的好处是否超过使用事件存储的额外麻烦。

考虑这样一种情况,聚合可以保存在进程的内存中,并且有一些并发控制保证不会同时执行针对该聚合的冲突命令。

这样做的主要好处是我们可以替换

的循环
  1. 从数据存储中读取
  2. 处理命令 1
  3. 写入数据存储
  4. 从数据存储中读取
  5. 处理命令 2
  6. 写入数据存储
  7. 从数据存储中读取
  8. 处理命令 3
  9. 写入数据存储

  1. 从数据存储中读取
  2. 处理命令 1
  3. 写入数据存储
  4. 处理命令 2
  5. 写入数据存储
  6. 处理命令 3
  7. 写入数据存储

无论持久性模型如何,我们都可以应用它。值得注意的是,从一个角度来看,我们只是让我们的应用程序成为一个 domain-specific 缓存,数据存储服务只是为了让我们的缓存持久(Adya, Myers, Qin, and Grandl (2009) 将此架构称为 LInK).

在 read-modify-write 方法中,数据存储处理的读取次数至少与写入次数一样多(假设有超过零个命令最终未更新状态(这将包括编码为命令的查询)),因此,即使以减慢写入速度为代价,也往往会针对快速读取进行优化(例如,可能会使用索引,这需要写入来更新索引)。

在 ...-modify-write 方法中,相反地,我们可以相对于处理的命令数进行少量读取,而写入次数大致相同。因此,我们希望对写入而不是读取进行更多优化。

在 update-in-place 持久化模型中,我们读取整个最新状态并(尤其是在更 key-value 方法中)更新整个状态(在其中一些模型中可能只发送字段通过线路更改,但数据存储通常会花费一些精力只更新那些字段)。在 event-sourced 持久化模型中,我们通常(在最坏的情况下假设我们正在执行一些快照)读取整个状态作为快照,然后读取自该快照以来的一些事件;写作时,我们只写一些小东西(假设我们对领域的建模比简单的 CRUD 操作更丰富)和要附加的原子。

在这里,与 update-in-place 模型相比,event-sourced 模型通常可以预期具有更昂贵的读取(尽管通常不那么频繁)和稍微便宜的写入。想象一下,在我们的“cache-but-durable”示例中,update-in-place 读取需要 20 毫秒,写入需要 20 毫秒,而 event-sourced 读取需要 40 毫秒,但写入需要 8 毫秒;命令处理本身低于 1 毫秒。根据我的经验,这些时间彼此相对准确。

然后处理 4 个命令 update-in-place 需要 20 + 4x20 = 100 毫秒(每个命令平均 25 毫秒) 处理 4 个命令 event-sourced 需要 40 + 4x8 = 72 毫秒(每个命令平均 18 毫秒)

注意:在分布式系统延迟测量中,均值 perhaps-surprisingly 可有效表征延迟,主要是 因为 它受异常值的影响不成比例(负延迟是不可能的) .

我提到的提供并发控制的一种方法是将聚合的传入命令队列与该聚合相关联。不是直接调用聚合上的方法来处理命令,而是将命令(连同处理命令结果的方法,例如回调或另一个队列)放入队列;同时,聚合正在从队列中拉出命令。这有时在 OO 中称为“活动对象”模式,而其他人可能将其识别为参与者模型。

有许多框架和工具包提供或支持 actor 模型:Erlang/OTP、Microsoft Orleans、Thespian (Python)、Akka、Akka.Net 和 ProtoActor 是一些值得注意的实现。

免责声明:我受雇于 Lightbend,该公司维护并提供围绕上述框架之一的专业 services/support。