CQRS:基于多个聚合事件的更高级别状态

CQRS: Higher level state based on multiple aggregates events

考虑以下场景:

我们有几个具有共同特征的聚合(例如:它们属于同一个用户) 每个聚合的事件用于实现其自身的状态。

如果需要更高级别的状态怎么办。该状态应代表所有用户聚合的总体状态。 考虑到有一个特殊的业务逻辑定义了更高级别状态的具体化(例如:如果超过 x 个聚合处于错误状态,则客户聚合状态需要更高级别的错误状态)

此要求涉及具体化多个聚合的事件。此外,更高级别的状态被认为是一个事实,因为它完全由较低级别的事件决定。因此,在这里使用 Saga 似乎是不合适的。

另一方面,需要在更高级别的状态发生转换时发出事件(例如:当超过x个聚合报告错误时,发出可以驱动进一步业务逻辑的事件)。因此基于其他事件生成事件似乎也不合适。

此处推荐的方法是什么?我可以看到 Saga 如何侦听较低级别的聚合事件并将其中一些转换为进入更高级别实体聚合的命令,后者又生成自己的事件,但在采用该路线时我无法逃避这种感觉event => command => 事件流是一个仪式,而不是真正的设计要求。

感谢您的见解

乔纳森

我认为你应该更多地关注你的业务而不是实施设计。是否有任何业务流程需要该信息?那就去实施吧。

没有低级别和高级别。只是对同一个复杂系统有不同的观点,为了理解它我们必须做不同的切片。这就像我们只能看到 2d,但世界是 3d。在某些情况下,您必须在现实中选择最适合您的部分。

在您的情况下,您选择在每个使用事件的聚合上保持强一致性,以维护用于命令验证的状态;他们用自己的事件来看待世界。但是你可以对世界有不同的看法,就像五蕴的看法一样真实。您可以有一个 Saga/Process 管理器来监听相关事件,并根据某些业务需求生成一些命令来进一步更改系统的状态。没关系,这就是复杂系统的工作方式。您只需要围绕这些流程设置明确的边界,并使用有界上下文和上下文映射将它们分开。

总而言之,继续围绕您确定的任何业务流程创建 Sagas。

乌迪大汉once wrote

Here’s the strongest indication I can give you to know that you’re doing CQRS correctly: Your aggregate roots are sagas.

在这种情况下:"it is required to issue events when a transition occurs on the higher level state" 是一个重要线索,表明有一个 "higher level aggregate" 正在观察较低级别的事件并应用您的业务逻辑来确定较高级别的状态是否已更改。

这个更高级别的聚合可能有一个类似

的接口
void handle(LowerLevelEvent e);

所以较低级别的聚合输出它们的事件,然后管道将该事件的副本传送到较高级别聚合的输入,较高级别聚合输出较高级别的事件。

请注意,这里没有任何魔法 -- 更高级别的聚合并未使用所有事件来决定更高级别的转换,它仅使用它目前知道的

整个流程看起来像

  • 下层聚合广播event:{id:123}给感兴趣的大家
  • 管道观察 event:{id:123},并作为响应将 handle(event:{id:123}) 命令 发送到更高级别的聚合
  • 更高级别的聚合应用自己的业务逻辑,在适当的情况下广播自己的事件

There no low level and high level.

这一点很重要;上面的低级别和高级标签实际上只是参考点。就聚合而言,只有它本身,以及其他一切——没有边界的层次结构。

除其他事项外,这意味着您可以测试 "higher level" 聚合的行为,甚至不存在较低级别的聚合 - 您只需向它发送消息并验证它是否正确反应。

在处理流程时,通常有两种方法。

编排

这依赖于一个 隐式 过程,让每个聚合参与一个过程,并且事件通常携带所有必需的信息。对我来说,当过程的一部分可以分成并行位时,这就会崩溃。进行任何基于人的交互也不适合这种情况。也看不到进程的状态。

编排

这是我们明确 跟踪流程和流程经理的地方(我更喜欢这个术语而不是 "saga")。这 可能 就是为什么有些人将所有聚合根视为流程管理器,但相同的聚合根可能参与不同的流程,这意味着如果我们确实将 AR 视为流程管理器,它将必须知道它属于哪个进程。

我更喜欢编排,因为您可以查看流程的当前状态。为此,我倾向于首先让我的流程经理成为 class 公民,因为他们 也是 聚合根,但只关注流程管理。

在有界上下文世界中,我会让我的流程管理(更高级别)充当一种集成层,因为一个流程可能涉及多个(较低级别)有界上下文。

例如,CustomerInteractionJournalEMail 可能是许多进程的一部分。

在我的较低级别 有界上下文级别,我会让我的消息处理程序响应命令,然后我会发布事件。我通常不会对那个级别的事件做出反应。

更高级别流程管理限界上下文中,我对通常启动流程的命令事件推动流程。