【问题标题】:Event Sourcing - Displaying older data事件溯源 - 显示旧数据
【发布时间】:2018-01-10 01:11:01
【问题描述】:
这是一个菜鸟(我认为)事件溯源
问题。
作为一个例子,我们有:
鞋匠鲍勃有:
- “shoemaker_business_locations”(营业地点地址)
- 第一个地址是“88d8 - 5 Baker Street”(1 月添加)
- 第二个地址是“73c6 - 6 Cadman Plaza”(三月添加)
- 在某个时间点(10 月),鞋匠从
“贝克街 5 号”到“贝克街 11 号”(街区内的几栋房子)
假设我们有一个“shoe_repair_order”(聚合根?)
问题:我们如何设计 Event-Sourced 系统来显示订单的旧地址?
- 我们是否必须始终为我们的订单存储一个事件,例如
“OrderLocationAdded”并在我们的订单事件中存储实际地址的副本 -
这样,每个订单都会有一个正确的旧位置,只需从事件中获取。
只有通过发出诸如“OrderLocationUpdated”之类的新事件,我们才能对其进行更新
如有需要,可用于该特定订单。
或
我们是否发出“OrderLocationAdded”事件引用地址
“88d8 AT VERSION 1”,以便我们知道要查找我们营业地点的 VERSION 1。
我们将不得不重播“shoemaker_business_locations”上的事件
直到我们看到地址的“版本 X”(在我们的例子中是版本 1)。
这就是我们必须对所有版本为
地址低于当前设置的版本(版本 1)。
-
其他方式?
也许我们的读取模型只是存储了不同版本的所有地址信息,当查询某个版本时,我们
不必在我们的写入模型中重放事件,而只需从我们的读取模型中获取版本 1 的地址?
【问题讨论】:
标签:
database
cqrs
event-sourcing
【解决方案1】:
我认为做到这一点的最佳方法最终取决于您如何设计域模型,但这是我解决问题的方法:
我们需要在我们的域模型中表示地址。这意味着,地址将是值对象、实体或聚合。在这三个中,地址实际上只能是该域中的值对象或实体,因为我们是修鞋应用程序而不是地址簿。实体具有某些属性,例如通过其 ID 进行区分,以及某些限制,例如仅作为一个聚合的一部分,地址似乎不符合。因此,我会看到一个地址是这个域中的一个值对象。
在“shoe_repair_order”聚合上定义 OrderLocationAdded 事件时,您需要决定是在事件中包含地址的完整详细信息,还是使用对地址的引用。由于地址是一个值对象(它是由其数据定义的,而不是由 ID 定义的),答案是在事件中包含值对象。这样,当您重播事件以构建订单的当前状态时,它仍然会保留原始地址的详细信息。
如果您还想在订单上显示新地址,您可以使用其他事件(例如 BusinessMovedToNewLocation)来显示新地址的详细信息,并将其添加到订单中。此事件不会替换原始地址,它只会让您显示企业现在位于不同位置的信息性消息。
【解决方案2】:
你的问题的答案当然取决于你如何定义你的领域模型(特别是你的聚合是如何定义的)。我解决这个问题的方法是有两个聚合:
ShoeMaker Aggregate(在您的情况下,它存储所有信息,例如鞋匠企业的名称、有关该企业所有者的信息、该鞋匠的所有地址列表,其中每个地址都可以包含电子邮件、电话号码等信息, 和一个名为 current 的 bool 字段,它对于当前地址可能是正确的。我想这个 AR 会有一个名为“ChangeShoeMakerCurrentAddress”的命令,它会引发一个名为“ShoeMakerAddressesChanged”的事件。
订单汇总(其中包含有关订单的所有信息,例如客户名称、价格、详细信息、订单日期、交货日期等)。
假设我们有一个名为“ShoeMakerOrderReadModel”的读取模型,它可以订阅由 ShoeMaker Aggregate 和 Order Aggregate 引发的事件。此读取模型侦听事件并将它们非规范化为 POJO/POCO 对象。这个 POCO/POJO 模型可以有一个鞋匠地址列表(其中每个地址可以有诸如电子邮件、电话号码和一个称为 current 的布尔字段,对于当前地址可能是真的。)这个 AR 绝对可以订阅“ShoeMakerAddressesChanged”来自 ShoeMaker Aggregate 的事件,然后更新鞋匠地址列表。
我希望这会有所帮助!
【解决方案3】:
感谢 CQRS,答案非常简单:你不能听BusinessMovedToANewLocationEvent 中的OrderDetailsReadModel。当OrderPlacedEvent 发生时,您只需在此Read model 中的Order details 中查找和存储一次当前 Business address。
通过这种方式,您不必在事件中包含任何Address value object。 Read model 完成所有工作。
如果出于其他原因需要,您可以收听该事件,但不要更新地址。例如,Read model 可以更智能,并另外保留一个addressChangedInTheMeanTime boolean 标志,以告诉Alice 她看到的Address 是旧的,但因此取决于您的业务需求。
【解决方案4】:
这里有几个选项:
- 在您的鞋匠阅读模型中,包括地址列表,包括它们开始生效的日期。这样,当您在订单投影中处理订单事件时,您可以查询鞋匠读取模型并获取与订单日期相符的地址(即使您稍后重建投影也可以使用)。这样做的缺点是两个视图之间的耦合(订单投影现在取决于鞋匠的查询 API,因此您需要确保鞋匠投影是最新且可用的)
- 如果您有一种订阅多个事件流的方式,以便以大致的全局顺序接收事件,则在您的订单投影中还可以处理鞋匠地址事件(创建和更新)。将鞋匠的迷你表保存到“当前”地址(表示您已处理的事件的当前时间,允许回放正常工作)。然后在处理订单创建事件时,只需在表中查找当前地址并将其复制到订单视图中即可。这需要更多代码,但避免了视图之间的依赖关系,并避免了鞋匠视图中对地址历史记录的需求(尽管出于其他原因您可能仍然需要它)。
- 将鞋匠地址作为订单域模型的一部分。它需要包含在订单创建命令中(可能通过控制器层中的命令丰富,在鞋匠阅读视图中查找)。这似乎没有必要,因为订单没有独立于商店概念的商店地址概念(这与送货地址不同,送货地址通常在某种程度上是每个订单的商品)。