18

关于 CQRS 我没有得到一件事:当引发的事件不包含更新读取模型所需的详细信息时,如何更新读取模型。

不幸的是,这是一个很常见的场景。

示例:我将用户添加到组中,因此我发送了 addUserToGroup(userId, groupId) 命令。这由命令处理程序接收、处理、创建、存储和发布 userAddedToGroup 事件。

现在,事件处理程序接收此事件和这两个 ID。现在应该有一个视图列出了所有用户及其所在组的名称。要更新该视图的读取模型,我们确实需要用户 ID(我们有)和组名(我们没有没有,我们只有它的 id)。

所以问题是:我该如何处理这种情况?

目前,我想到了四个选项,它们都有其特定的缺点:

  1. 读取模型询问域。=> 禁止,甚至不可能,因为域只有行为,没有(公共)状态。

  2. 读取模型从读取模型中的另一个表中读取组名。=> 有效,但是如果没有匹配表怎么办?

  3. 将必要的数据添加到事件中。=> 不起作用,因为这意味着我还必须更新所有以前的事件,而且我无法预见有一天我可能需要哪些数据。

  4. 不要通过“通常的”事件处理程序来处理事件,而是在后台启动一个 ETL 进程来处理事件存储,创建必要的数据并写入读取模型。=> 工作,但对我来说,对于这样一个简单的场景来说,这似乎有点太多的开销。

所以,问题是:我该如何正确处理这种情况?

4

3 回答 3

7

有两种常见的解决方案。

1)“事件丰富”是您确实在事件上放置反映您所提及信息的信息的位置,例如组名称。这样做介于正确建模您的域和作弊之间。例如,如果您知道组名称发生了变化,那么在更改时发出名称并不是一个坏主意。想象一下,当您在报价单或发票上创建行项目时,您希望在发票创建事件上发出所售商品的价格。这是因为您必须遵守该价格,即使它稍后发生变化。

2) 一次投射多个流。编写一个投影仪,它可以观察来自各种流的信息并将它们连接在一起。您可能会观看用户和组事件以及您的用户添加到组事件。根据系统中事件的顺序,您可能在知道组名称之前就知道用户在组中,但在开始之前您应该知道事件存储的一般属性。

于 2012-09-09T16:24:10.247 回答
5

事件不一定代表首先启动进程的命令的一对一映射。例如,如果您有一个命令:

SubmitPurchaseOrder
    Shopping Cart Id
    Shipping Address
    Billing Address

生成的事件可能如下所示:

PurchaseOrderSubmitted
    Items (Id, Name, Amount, Price)
    Shipping Address
    Shipping Provider
    Our Shipping Cost
    Shipping Cost billed to Customer
    Billing Address
    VAT %
    VAT Amount
    First Time Customer
    ...

通常,信息对域模型可用(通过命令提供或作为相关聚合的已知内部状态或通过作为处理的一部分计算。)

此外,可以通过在处理期间查询读取模型甚至不同的 BC(例如,根据状态检索实际的增值税百分比)来丰富事件。

您正确地假设事件可以(并且可能会)随着时间而改变。如果您使用版本控制,这基本上无关紧要:添加新事件(例如SubmitPurchaseOrderV2)并向所有应该使用它的类添加适当的事件处理程序。不需要更改旧的事件,它仍然可以被消费,因为你不修改接口,你扩展它。这基本上归结为实践中开放/封闭原则的一个很好的例子。

于 2012-09-09T21:33:13.037 回答
0

选项 2 很好,您关于“组名称读取模型表中的不匹配怎么办”的问题将不适用。不应删除任何数据,应在先前的事件(例如删除组)被触发时失效。最后,组表中的行有效,您可以毫无问题地读取组名。唯一明显的问题可能是速度不一致,但这是另一个问题,无论处理速度如何,事件都应该有序处理。

于 2013-04-18T14:27:11.543 回答