如何使用 CQRS 和事件溯源处理依赖于应用程序中现有记录的命令

How to deal with Command which is depend on existing records in application using CQRS and Event sourcing

我们将 CQRS 与 EventSourcing 结合使用。

在我们的应用程序中,我们可以从 ui 添加资源(这是单个项目的业务术语),我们正在相应地发送命令以添加资源。

所以我们在应用程序中存在 x 数量的资源,这些资源是之前添加的。 现在,我们有了一种特殊类型的资源(我称它为 SpecialResource)。 当我们添加这个 SpecialResource 时,id 需要与应用程序中的所有现有资源相关联。 链接意味着这个 SpecialResource 应该有 List of ids(guids) (List) of existing resources.

在添加特殊资源之前,我们试图在applcation中获取所有资源id的解决方案 资源(即在触发 AddSpecialResource 命令之前)。 将这些List分配给SpecialResource,然后发送AddSpecialResource命令。

但我们不应该这样做,因为根据 cqrs 命令不应该查询。 IE。命令不能依赖于查询,因为查询可能有过时的记录。

如何在不查询应用程序中现有记录的情况下实现这种业务场景?

很难说出您的数据库的能力,但是添加 "snapshot" 的最一致的方式是在数据库层,因为在纯 CQRS 中没有其他常见的地方可以做到这一点。 (有一些关于做 CQRS+ES 快照的文章,如果那是您实际尝试使用 SpecialResource 实现的目标的话)。

一种方法可能是在 AddSpecialResource 命令(在数据库中)到达时使用某种存储过程来具体化 ID 列表。

另一种方法是用一些标记(时间戳)捕获"all existing resources (up to the moment)",从不删除旧资源,并在查询中添加"SpecialResource"条件,这将使用SpecialResource数据。

好吧,还有一个选择(取决于您手头的情况)是始终在同一个查询中使用方便的 ID 列表,该查询服务于 UI。这样 "all resources" 的定义就变成了 "all resources as seen by the user (at some moment)".

我认为任何计算机系统都不会 100% 一致,仅仅是因为生活不会,也不可能像这样工作。显然我们都生活在过去,因为你的大脑需要时间来处理输入。

要点是,您已尽最大努力利用手头的信息,但要确保您的系统能够消除任何边缘。因此,如果您需要将一两个资源与您的 SpecialResource 相关联,那么您应该能够这样做。

因此,即使您 可以 将您的 SpecialResource 与数据存储中的所有现有条目相关联,也就是说没有其他资源尚未已进入系统也需要关联

像往常一样,这一切都将取决于您的具体用例。这就是为什么流程管理器连同他们的状态,使人们能够处理该状态,直到流程完成。

希望我没有误解你的问题:)

你可以做两件事来解决这个问题:

  • 区分读写模式。你知道什么是阅读模型,对吧?因此 "write model" 相反,数据是数据结构和行为的组合,足以强制执行所有不变量并生成一致的事件作为每个执行命令的结果。

  • 不要接受过于字面意思的 "Event Store is a single source of truth" 规则。考虑以下解释:ES 是您的应用程序的所有事实的单一来源,但是,对于每个特定命令,您可以创建 "write models" 这将提供足够的 "truth" 以使该命令保持一致。

But we are not suppose to do so , because as per cqrs command should not query. I.e. command cant depend upon query as query can have stale records.

这不太正确。

"Commands" 运行 一直查询。如果您正在使用事件溯源,在大多数情况下您的命令 查询 -- "if this command were permitted, what events would be generated?"

这与您描述的情况之间的区别在于聚合边界,它在事件源域中是事件流的奇特名称。在处理命令时,聚合可以 运行 查询其自己的事件流(也就是说,它自己的状态)。超出范围的是其他聚合(事件流)。

实际上,这意味着 如果 SpecialResource 确实需要与其他资源 ID 在事务上保持一致,那么所有这些数据都需要成为同一聚合的一部分,因此是同一个事件流的一部分,从那一点开始的一切都非常简单。

因此,如果到目前为止您一直在使用单独的流对资源进行建模,现在您需要 SpecialResource 才能像您描述的那样工作,那么您需要对域模型进行相当大的更改。

好消息:这可能不是您的真正要求。考虑您到目前为止所描述的内容 - 如果 resourceId:99652 在 SpecialResource 之前一毫秒创建,那么它应该包含在 SpecialResource 的状态中,但如果它是在一毫秒之后创建的,那么它不应该。那么,如果资源在 SpecialResource 丢失前一毫秒创建,业务成本是多少

因为,先验的,这听起来不像是应该太贵的东西。

更常见的是,真正的需求看起来更像 "SpecialResource needs to include all of the resource ids created prior to close of business",但在营业结束后 5 分钟之前,您实际上并不需要 SpecialResource。换句话说,您在这里有一个 SLA,您可以使用该 SLA 更好地通知您的命令。

How can we achieve this business scenario without querying existing records in application?

转身; 运行 查询,将查询结果(资源 ID)复制到创建 SpecialResource 的命令中,然后分派要传递给域模型的命令。 CreateSpecialResource 命令在其中包含正确的资源 ID 列表,因此聚合无需担心如何发现该信息。