1

我正在为教育目的开发一个简单的基于 DDD + 事件源的应用程序。

为了在存储到事件存储之前设置事件版本,我应该查询事件存储,但我的直觉告诉这是错误的,因为它会导致并发问题。

我错过了什么吗?

4

2 回答 2

6

对此有不同的答案,具体取决于您正在考虑的用例。

通常,事件存储是一个愚蠢的、与域无关的设备。它表面上类似于List抽象——它存储你放入其中的内容,但它实际上并没有做任何工作来满足你的域约束。

在您的事件流只是已经发生的事情的持久记录的用例中(意味着:您的域模型没有获得否决权;记录事件不依赖于先前记录的事件),那么附加语义就可以了,并且取决于在您使用的设备类型上,您可能不需要知道您正在写入的流中的哪个位置。

例如:GetEventStore 的 API 理解ExpectedVersion.ANY为将这些事件附加到流的末尾,无论它发生在哪里。

如果您确实关心以前的事件(域模型应确保基于其先前状态的不变量),那么您需要做一些事情来确保将事件附加到您检查过的同一历史记录中。最常见的实现会传达写入光标在流中的预期位置,以便设备可以拒绝写入错误位置的尝试(这可以保护您免受并发修改)。

这并不一定意味着您需要查询事件存储来获取位置。您可以在加载流时计算流中的事件数量,并记住您添加了多少事件,因此如果您仍然与流同步,流“应该”在哪里。

我们在这里所做的类似于比较和交换操作:我们得到流的原始状态的表示,创建一个新的表示,然后比较和交换对原始的引用,而不是指向我们的更改

oldState = stream.get()
newState = domainLogic(oldState)
stream.compareAndSwap(oldState, newState)

但是因为流是一种仅具有附加语义的持久数据结构,所以我们可以使用不需要复制现有状态的简化 API。

events = stream.get()
changes = domainLogic(events)
stream.appendAt(count(events), changes)

如果您的设备的 API 不允许您指定写入位置,那么可以 - 当其他写入器在您对位置的查询和您的写入尝试之间更改流的位置时,存在数据竞争的危险。查询中获得的数据总是陈旧的;除非您持有锁,否则您无法确定在读取本地副本时数据在源头没有更改。

于 2018-04-11T17:40:31.960 回答
1

我想你不应该考虑事件版本。

如果您谈论事件流中的位置,一般来说,没有保证在创建时刻确定它的方法,只能在处理时间或事件存储中确定。

如果它与事件版本完全相关(请参阅http://cqrs.nu/Faq,我如何版本/升级我的事件?),您将其硬编码在您的应用程序中。所以,我的意思是下一个用例:

首先,您有一个应用程序生成一些事件。接下来,您更新应用程序并更改事件(添加一些字段或更改有效负载结构)但保持逻辑含义。因此,现在您的 ES 中有旧事件和新事件,它们与旧事件有很大不同。并且为了区分你使用事件版本,例如 0 和 1。

于 2018-04-13T10:17:57.670 回答