AMG*_*AMG 5 domain-driven-design cqrs event-sourcing
所以我试图找出 CQRS+ES 架构的一般用例背后的结构,我遇到的问题之一是聚合在事件存储中的表示方式。如果我们将事件划分为流,那么流究竟代表什么?在跟踪一系列项目的假设库存管理系统的上下文中,每个项目都有一个 ID、产品代码和位置,我无法可视化系统的布局。
从我在互联网上收集到的信息来看,它可以简洁地描述为“一个聚合一个流”。所以我会有一个 Inventory 聚合,一个包含 ItemAdded、ItemPulled、ItemRestocked 等事件的单个流,每个事件都带有包含项目 ID、更改数量、位置等的序列化数据。聚合根将包含一个 InventoryItem 对象的集合(每个都有它们各自的数量、产品代码、位置等)这似乎允许轻松执行域规则,但我看到了一个主要缺陷;将这些事件应用于聚合根时,您必须首先重建该 InventoryItem 集合。即使使用快照,对于大量项目,这似乎也非常低效。
另一种方法是让每个 InventoryItem 有一个流跟踪与仅项目有关的所有事件。每个流都以该项目的 ID 命名。这似乎是更简单的路线,但现在您将如何执行域规则,例如确保产品代码是唯一的,或者您不会将多个项目放入同一位置?看起来您现在必须引入 Read 模型,但将命令和查询分开难道不是重点吗?只是感觉不对。
所以我的问题是“哪个是正确的?” 部分两者?两者都不?像大多数事情一样,我学得越多,我就越知道我不知道......
在典型的事件存储中,每个事件流都是一个隔离的事务边界。每当您更改模型时,您都会锁定流,附加新事件,然后释放锁定。(在使用乐观并发的设计中,边界是相同的,但“锁定”机制略有不同)。
您几乎肯定希望确保任何聚合都包含在单个流中 - 在两个流之间共享聚合类似于在两个数据库之间共享聚合。
单个流可以专用于单个聚合、聚合集合,甚至整个模型。属于同一流的聚合可以在同一事务中更改 - 哇!-- 以从流加载聚合时发生一些争用和一些额外工作为代价。
最常讨论的设计将每个逻辑流分配给单个聚合。
这似乎可以轻松执行域规则,但我看到了一个主要缺陷;当将这些事件应用到聚合根时,您必须首先重建 InventoryItem 的集合。即使使用快照,对于大量项目来说效率似乎也非常低。
有几种可能性;在某些模型中,特别是那些具有很强时间成分的模型,将某些“实体”建模为聚合的时间序列是有意义的。例如,在调度系统中,Bobs Calendar您可能会使用Bobs March Calendar,Bobs April Calendar而不是 ,等等。将生命周期分成较小的部分可以控制事件计数。
另一种可能性是快照,它还有一个额外的技巧:每个快照都用元数据进行注释,该元数据描述了流中快照的制作位置,您只需从该点向前读取流即可。
当然,这取决于支持随机访问的事件流的实现,或者允许您后进先出读取的流的实现。
请记住,这两者实际上都是性能优化,而优化的首要规则是……不要。