【问题标题】:Is my understanding of CQRS/ES correct?我对 CQRS/ES 的理解正确吗?
【发布时间】:2017-08-01 16:14:34
【问题描述】:

我不经常在 StackOverflow 上写东西,但我想分享一下我对 CQRS/ES 实现的理解的想法。我对不同组件的职责感到很困惑,我想我终于明白了这个话题。如果您能告诉我我的理解是否正确,那就太好了。

一步一步:

  1. 用户创建并触发一个命令,该命令是一个对象,表示在域中执行请求的更改所需的最小值。该命令已分配处理函数。在使用 CommandBus 的场景中,分离的 Dispatcher 将触发 CommandHandler 执行操作。 CommandBus 可以使用任何消息传递系统、数据库来实现,也可以只在内存中工作。

  2. CommandHandler的职责是验证命令,然后根据业务规则创建一个或多个Events并发送给EventStore。

  3. 可以在聚合上调用命令。聚合是由一个或多个实体和值对象组成的对象。 Aggregate 的目的是不仅可以验证命令本身,还可以在“聚合”状态的上下文中验证命令。

  4. EventStore 可以使用消息传递系统或数据库来实现。事件总是以过去分词动词命名,例如 OrderConfirmed。它们由 EventHandlers 使用。

  5. EventHandler 是一个负责“投影”的函数,这意味着在数据库中实际保存内容。它还可以执行其他操作,例如发送电子邮件,一切都取决于事件的初始目的。

如果我有任何错误,请纠正我。我还有几个问题。

  1. 在 CommandHandler 生成多个事件以将多个对象保存在数据库中的情况下,我是否可以为它们分配相同的 UUID,以便稍后我还将保存在所有投影对象中?

  2. EventHandler 可以触发另一个命令或创建另一个事件吗?在这种情况下,最佳做法是什么?

感谢您的回答。

【问题讨论】:

  • 听说过分而治之吗? =P
  • @plalx 如果您指的是我的第一个。问题不是个案。无论如何,据我所知 CommandHandler 可以生成多个事件。
  • 我指的是整个问题。

标签: domain-driven-design cqrs event-sourcing


【解决方案1】:

命令是一条消息,其中包含描述需要发生的操作的所有必要信息。

关联的命令处理程序在聚合根上调用命令。聚合根验证命令,然后才分派一个或多个事件。事件处理程序负责将数据投射到读取存储中。 Sagas 或状态处理器可用于订阅事件和发布其他消息。

验证需要在聚合根中进行。命令处理程序是另一个可以放置简单数据验证的地方。但是,事件处理程序不是数据验证的地方。如果将验证放在事件处理程序中,命令将成功执行,但最终会在读取存储和订阅事件的任何系统中得到不一致的数据。

事件确实代表了过去由有效命令/聚合根分派的内容。每个事件都必须被认为是有效的,因为一旦您重新加载数据,相应的聚合根必须重播它。聚合根中的业务逻辑可能会随着时间而改变,但不应影响事件处理程序。可以使用多个版本的事件和相同类型的相应处理程序来实现。

所有事件,包括有关聚合根的信息(类型、UUID 等)都应存储在事件存储中。事件存储必须一致且持久,因为事件是数据源。

我希望这些信息能让您了解事件处理的工作原理。现在让我试着回答问题。

  1. 所有这些事件(通常)应该具有相同的聚合根 ID 和类型(命名空间 + 类),因此它们可以由相应的聚合根实例重放。它们还应该有一个版本号来确定它们发生的顺序。

  2. 我在第二段中介绍了这一点。

【讨论】:

    【解决方案2】:

    一个命令,它是一个对象,表示在域中执行请求的更改所需的最小值

    完全正确。它是一条消息,或者至少是一条消息的表示,而不是“对象”。 Applications aren't object oriented at the boundaries,领域模型也不是。

    EventHandler的职责是验证命令,然后根据业务规则创建一个或多个Events并发送给EventStore。

    这有点混乱 - 你可能指的是那里的命令处理程序

    可以在聚合上调用命令。

    非常接近 - 说在 Aggregate Root 上调用该命令更为正确。在最初的 Evans 公式中,聚合将在其边界内指定一个实体作为聚合根,也就是说,它将是所有命令和查询的一个访问点。

    在 CQRS 文献中,命令和查询是分开处理的;聚合及其根仅用于命令。

    EventStore 可以使用消息系统或数据库来实现。

    EventStore 扮演着记录簿的角色;这意味着它需要支持最低持久性保证 - 在您“确定”事件将可用于在重启时重新加载聚合之前,不应允许任何人查看事件。

    事件总是以过去分词动词命名,例如 OrderConfirmed。它们由 EventHandlers 使用。

    事件,就像命令一样,只是消息;它们可以以任何你喜欢的方式食用。

    EventHandler 是一个负责“投影”的函数,这意味着在数据库中实际保存内容。

    “投影”通常被理解为是指对某些事件历史(由一个或多个来源产生)创建非权威表示。其动机是,在从历史中创建表示时通常需要做大量的工作。如果您正在尝试支持低延迟查询 - 并且对更改发生时间和更改可见之间的一些时间间隔感到满意,您可以通过创建更适合快速查询的数据结构来改善延迟(或者,或者,只需预加载一个带有查询答案表示的缓存)。

    注意:非权威性很重要。该数据库不是记录簿,而是事件存储。

    在 CommandHandler 生成多个事件以将多个对象保存在数据库中的情况下,我可以为它们分配相同的 UUID,以便稍后我还将保存在所有投影对象中吗?

    这个问题不清楚。投影对象将从事件构造,因此您通常会期望投影对象中的任何 UUID 都是您从事件中的数据派生的东西。

    此外,生成事件的通常不是命令处理程序——这是领域模型的职责;这意味着它发生在聚合根之后。

    EventHandler 可以触发另一个命令或创建另一个事件吗?在这种情况下,最佳做法是什么?

    一种常见的模式是进程管理器,它是一个状态机:您将事件传递给它以从一种状态转换到另一种状态,然后查询它以找出不应该分派哪些命令。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-27
      • 2021-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-02
      • 2021-02-14
      相关资源
      最近更新 更多