如何在 CQRS 中处理读取模型

How to process Read Model in CQRS

我们想在我们的新设计中实现 cqrs。我们对处理命令处理程序和读取模型有一些疑问。我们了解到,在处理命令时,我们应该对 aggregateId 进行乐观锁定。但是在处理 readModels 时应该考虑什么方法。我们应该锁定整个 readModel 还是锁定 aggregateId,或者在处理读取模型时从不锁定。

case 1. 对整个readmodel进行lock -> 最安全但速度较差

情况 2 - 锁定 aggregateId。这里可能会出现两个问题。如果我们明智地锁定 aggregateId -> 那么如果读取模型服务器重新启动怎么办。它不知道从哪里重新开始。

情况 3 - 从不锁定。在这种方法中,我认为数据可能处于损坏状态。例如,生成了一个订单插入事件,并且经过一些 workflow/saga,订单更新事件也发生了。如果订单更新事件先发生而订单插入事件尚未处理怎么办?

希望我能够解决我的问题。

For eg say an order inserted event is generated and thorugh some workflow/saga, order updated event took place as well. what if order updated event comes first and order inserted event is not yet processed ?

如果您考虑读取模型轮询有序的事件序列,而不是对无序的通知做出反应,那么它们通常更容易推理。

单个读取模型可能依赖于来自多个聚合的事件,因此聚合锁定不太可能是您最普遍的答案。

这也意味着,如果我们进行轮询,我们需要跟踪多个数据流的位置。换句话说,我们的读取模型可能包含元数据,告诉我们使用了每个源的哪个版本。

锁定可能取决于您的后备存储/缓存的性质。但乐观的做法

  1. 读取当前表示
  2. 计算新表示
  3. 比较和交换

同样,通常很容易推理。

如果您不在 Readmodel 中并发处理事件,则不需要锁。当您有一个 Readmodel 实例时就是这种情况,可能在微服务中轮询事件并按顺序处理它们。

如果您有同步读取模型(即与 Writemodel/Aggregate 在同一进程中),那么您很可能需要锁定。

要牢记的重要一点是,Readmodel 很可能与 Writemodel 不同。可能有很多 Writemodel 类型,它们的事件被投射到同一个 Readmodel 中。例如,在电子商务商店中,您可以有一个 ListOfProducts 来投影来自 VendorProduct 聚合的事件。这意味着,当我们谈论 Readmodel 时,我们不能简单地引用 "Aggregate",因为不涉及单个聚合。在电子商务的情况下,当我们说 "the Aggregate" 时,我们可能指的是 Product 聚合或 Vendor 聚合。

但是锁什么?这里要看数据库技术了。您应该锁定最小的 affected 读取实体或可以锁定的集合。在包含产品列表(读取实体,而不是聚合!)的 Readmodel 中,当一个事件只影响一个产品时,您应该只锁定该产品(即 ProductTitleRenamed)。

如果一个事件影响更多产品,那么您应该锁定整个系列。例如,VendorWasBlocked 影响所有产品(它应该删除该供应商的所有产品)。

您需要锁定具有非幂等副作用的事件,以防 Readmodel 的更新程序在事件处理期间失败,如果您想从它离开的地方 retry/resume。如果事件具有幂等的副作用,那么它可以安全地重试。

为了知道在 Readmodel 失败的情况下从哪里恢复,您可以在 Readmodel 中存储最后处理的事件的序列。在这种情况下,如果实体更新成功,那么最后处理的事件序列也会被保存。如果失败,则您知道该事件未被处理。