0

Aggregate可以使用View这个事实是在Vaughn Vernon's book 中描述的:

此类读取模型投影经常用于向各种客户端(例如桌面和 Web 用户界面)公开信息,但它们对于在有界上下文及其聚合之间共享信息也非常有用。考虑发票聚合需要一些客户信息(例如,姓名、账单地址和税号)以计算和准备正确发票的场景。我们可以通过 CustomerBillingProjection 以易于使用的形式捕获此信息,这将创建和维护 CustomerBilling-View 的专有实例。此读取模型可通过名为 IProvideCustomerBillingInformation 的域服务用于发票聚合。在幕后,这个域服务只是在文档存储中查询适当的 CustomerBillingView 实例

让我们想象一下,我们的应用程序应该允许创建许多用户,但具有唯一的名称。命令/事件流:

  • CreateUser{Alice}发送的命令
  • UserAggregate检查UsersListView,因为没有名为 Alice 的用户,聚合决定创建用户并发布事件。
  • UserCreated{Alice}事件发布 // 由 UserAggregate
  • UsersListProjection已处理UserCreated{Alice}// 为简单起见,让我们认为 UsersListProjection 只是在收到时累积用户名UserCreated event
  • CreateUser{Bob}发送的命令
  • UserAggregate检查UsersListView,因为没有名为 Bob 的用户,聚合决定创建用户并发布事件。
  • UserCreated{Bob}事件发布 // 由 UserAggregate
  • CreateUser{Bob}发送的命令
  • UserAggregate检查UsersListView,因为没有名为 Bob 的用户,聚合决定创建用户并发布事件。
  • UsersListProjection处理过UserCreated{Bob}
  • UsersListProjection处理过UserCreated{Bob}

问题是 -UsersListProjection没有时间处理事件并且包含不相关的数据,聚合使用了这些不相关的数据。结果 - 创建了 2 个同名用户。

如何避免这种情况?如何使聚合和预测一致?

4

2 回答 2

1

如何使聚合和预测一致?

在一般情况下,我们不会。预测与过去某个时间的聚合一致,但不一定具有所有最新更新。这是重点的一部分:我们放弃“即时一致性”以换取其他(更高杠杆率)的好处。

您提到的重复通常以不同的方式解决:通过对记录簿使用条件写入。

在您的示例中,我们通常会设计系统,以便将 Bob 写入我们的数据存储的第二次尝试会因为冲突而失败。此外,我们通过确保对数据存储的写入发生在任何事件可见之前来防止重复传播。

实际上,这给了我们一个“先写者获胜”的写作策略。失去数据竞争的作家必须重试/失败/等等。

(通常,这取决于创建 Bob 的两个尝试都使用相同的锁将该信息写入同一个位置的想法。)

降低冲突概率的常见设计是不使用聚合本身的“读取模型” ,而是使用数据存储中自己的数据。这并不一定会消除所有数据竞争,但您会减小窗口的宽度。

最后,我们求助于回忆、猜测和道歉

于 2021-04-30T15:35:03.233 回答
1

在 CQRS 中重要的是要记住,每个写入模型也是验证命令所需的读取的读取模型。这些读数是:

  • 检查是否存在具有特定 ID 的聚合
  • 加载整个聚合的最新版本

通常,CQRS/ES 实现将为您提供该读取模型。如何实施的细节将取决于实施。

这些是命令处理程序需要执行的唯一读取,并且如果可以用不超过这些读取来回答查询,则可以将查询表示为命令(例如GetUserByName{Alice}),该命令在处理时不会发出事件。这种只读命令的好处是它们可以高度一致,因为它们仅限于单个聚合。当然,并不是所有的查询都可以这样表达,如果查询可以容忍最终的一致性,那么可能不值得为强一致性支付协调税,而通常通过将其设为只读命令来支付这种协调税。(仅限于单个聚合的命令处理通常是高度一致的,但在某些情况下,例如,当事件形成 CRDT 并且聚合可以存在于多个数据中心中时,即使这种一致性也被放松了)。

所以考虑到这一点:

  • CreateUser{Alice}已收到
  • 用户Alice不存在
  • 坚持UserCreated{Alice}
  • CreateUser{Alice}已确认(例如 HTTP 200、对 *MQ 的确认、Kafka 偏移提交)
  • UserListProjection更新自UserCreated{Alice}
  • CreateUser{Bob}已收到
  • 用户Bob不存在
  • 坚持UserCreated{Bob}
  • CreateUser{Bob}承认
  • CreateUser{Bob}已收到
  • 用户Bob已经存在
  • 现有用户的命令处理程序拒绝该命令并且不保留任何事件(它可能会记录尝试创建重复用户)
  • CreateUser{Bob}确认失败(例如 HTTP 401、对 *MQ 的确认、Kafka 偏移提交)
  • UserListProjection更新自UserCreated{Bob}

请注意,虽然UserListProjection可以回答“该用户是否存在?”的问题,但写入端也可以(并且更一致地)回答该问题的事实本身并没有使该投影变得多余。 UserListProjection还可以回答诸如“谁是所有用户?”之类的问题。或“哪些用户的名字中有两个连续的元音?” 写入方无法回答。

于 2021-04-30T18:15:23.370 回答