如何获取 CQRS 模式中的聚合状态?

How to get state of an aggregate in CQRS pattern?

根据 Martin Fowler 和 Microsoft CQRS Journey 的文章,CQRS 是一种应用于 BC 的模式,而不是整个系统的架构。我对如何从 CQRS 中的任何外部内容获取聚合状态感到困惑。

一个聚合应该有一个命令 Get 到 return 它在写入模型中的状态,还是在读取模型中的相应查询?

这是Akka Platform Guide中的购物车服务示例。

ShoppingCart是一个集合,它有三个命令:AddItemCheckoutGet。在Get的命令处理程序中,它向命令发送者回复购物车摘要。这样,每个聚合都有一个命令 Get 到 return 其在写入模型中的状态。

但我想 Get 完全是一个查询,而不是命令。因为在 CQRS 模式中,命令会更改聚合的状态并触发事件,但 return 什么都没有。另一方面,查询 returns 聚合当前状态的副本,但没有任何改变。所有命令都存在于写模型中,所有查询都存在于读模型中。如果我想获得状态,我不应该发送命令来编写模型,而是发送查询来读取模型。最终一致性由写模型到读模型的事件投影来维护。

因此,ShoppingCartGet 应该移入读取模型。任何外部想要获得 ShoppingCart 的状态,它应该发送查询 GetShoppingCart 并最终得到回复 Summary。但这样一来,状态可能就陈旧了。它应该在一致性上有问题吗?

哪个设计是必要的和更好的?

Get 放入读取模型会带来一致性风险,否则将其放入写入模型会产生语义歧义。那是我的困惑。

谢谢。

Putting Get in read model gets risk of consistency, putting it in write model gets semantic ambiguity otherwise. That's my confusion.

首先,现实检查;从这些像素出现在屏幕上到您的眼睛看到它们之间的时间大约为一纳秒。所以你看到的答案至少是那么旧。这将花费什么,至少一毫秒?跨网络跳跃。

换句话说,查询响应已经很旧了。确保它仍然准确表示聚合的 当前 状态的唯一方法是锁定所有命令,直到您完成查看它。

如果必须,您可以这样做,但需要权衡取舍。据我所知,锁定是合适选择的情况很少见。但如果你需要它,你就需要它。注意:在这种情况下,您可能不得不放弃 CQRS。


更灵活的框架是查询处理程序returns,不是聚合“现在”的表示,而是(最近)过去某个特定时间点聚合的表示。

所以我在 12:02 发送了一些查询,我得到的是一份报告的副本,该副本是根据 12:00 的聚合状态副本准备的。而我,客户,知道这一点,因为在报告的顶部有一个用友好的大字母写的公告,上面写着“这份报告是在 12:00 准备的”。


这会引发很多有趣的问题,因为现在您正在谈论时间和 SLO(最新的报告是否足够好,或者我们需要更新的东西?我们可以缓存多长时间报告,与检查更新版本相比?我们愿意用一些额外的响应延迟来换取更新的版本吗?当没有更新版本的报告可用时,系统应该发生什么?)


I'm not sure if it should put a command Get into write model to return state of aggregate. I prefer to add query Get into read model

我就是这样做的 - 查询处理程序加载“读取模型”的副本并从中收集信息。不会加载“写入模型”。

我不熟悉 Akka,但我是这样看的:

最终一致性不是在写入端和读取端之间,而是在分布式系统中的复制(投影、读取模型、索引等)之间。

在写入模型中支持 Get 没有什么意义,因为读取和写入不必彼此一致。他们根本不必使用相同的概念。读取模型甚至可以是多个聚合的投影(理论上)。然而,有时,在写入模型中使用更简单的瞬态投影是有用的,但它们应该只在写入操作期间使用,而不应公开用于纯读取。

请记住,写入模型的唯一目的是投射足够的状态,以便能够处理和验证命令,并产生事件。就是这样。

我不确定我是否回答了你的问题,但这就是我看待一致性和 read/write 模型等的方式。无论如何,欢迎来到 Stack Overflow!

我收到了团队的回复。

出于多种原因,他们在写入端演示了 Get 命令:

  1. 显示处理不产生事件的命令的功能。
  2. 举例说明有时需要一致性,这在单个聚合的范围内是可能的。

就是这样。