Domain driven design 是否应在事件源中查询最新的事件版本?

Domain driven design 是否应在事件源中查询最新的事件版本?,domain-driven-design,cqrs,event-sourcing,event-stream,eventstoredb,Domain Driven Design,Cqrs,Event Sourcing,Event Stream,Eventstoredb,我正在开发一个简单的基于DDD+事件资源的应用程序,用于教育目的 为了在存储到事件存储之前设置事件版本,我应该查询事件存储,但我的直觉告诉我这是错误的,因为它会导致并发问题 我遗漏了什么吗?根据您正在考虑的用例,有不同的答案 通常,事件存储是一个哑的、不知道域的设备。它表面上类似于一个列表抽象——它存储了您在其中输入的内容,但实际上它不做任何工作来满足您的域约束 在您的事件流只是已发生事件的持久记录的用例中,这意味着:您的域模型没有否决权;记录事件不依赖于以前记录的事件,那么附加语义就可以了,并

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

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


我遗漏了什么吗?

根据您正在考虑的用例,有不同的答案

通常,事件存储是一个哑的、不知道域的设备。它表面上类似于一个列表抽象——它存储了您在其中输入的内容,但实际上它不做任何工作来满足您的域约束

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

例如: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不允许您指定写入位置,那么是的-当其他写入程序在您的位置查询和尝试写入之间更改流的位置时,存在数据争用的危险。查询中获得的数据总是过时的;除非您持有一个锁,否则您无法确保在读取本地副本时数据在源位置没有更改。

根据您正在考虑的用例,有不同的答案

通常,事件存储是一个哑的、不知道域的设备。它表面上类似于一个列表抽象——它存储了您在其中输入的内容,但实际上它不做任何工作来满足您的域约束

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

例如: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不允许您指定写入位置,那么是的-存在数据丢失的危险
当其他写入程序在您的位置查询和尝试写入之间更改流的位置时,进行竞赛。查询中获得的数据总是过时的;除非您持有锁,否则无法确保在读取本地副本时数据源没有更改。

我想您不应该考虑事件版本

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

如果它完全是关于事件版本的,请参见“如何对我的事件进行版本设置/升级?”,您可以在应用程序中对其进行硬编码。所以,我指的是下一个用例:


首先,您有一个生成一些事件的应用程序。下一步,您更新应用程序,并更改事件—添加一些字段或更改有效负载结构,但保留逻辑意义。所以,现在您的ES中有了旧事件,以及与旧事件显著不同的新事件。为了区分两者,您使用事件版本,例如0和1。

我想您不应该考虑事件版本

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

如果它完全是关于事件版本的,请参见“如何对我的事件进行版本设置/升级?”,您可以在应用程序中对其进行硬编码。所以,我指的是下一个用例:


首先,您有一个生成一些事件的应用程序。下一步,您更新应用程序,并更改事件—添加一些字段或更改有效负载结构,但保留逻辑意义。所以,现在您的ES中有了旧事件,以及与旧事件显著不同的新事件。为了区分两者,您使用事件版本,例如0和1。

如果您在事件存储中有一个约束,即aggregateType aggregateId version上有一个唯一的约束,则不会导致并发问题。不确定在事件存储中存储聚合类型是否是一个好主意,列为:'aggregateId,version,eventData'这取决于您的设计。在矿山设计中,相同的ID值可以由不同的聚合类型共享。@SHM我编写了聚合类型非事件type@SHM如果您使用的是eventstore.org,那么您是安全的;这些家伙保护您的事件流不受并发问题的影响;您可以在此处阅读更多内容:如果您在事件存储中有一个约束,即aggregateType aggregateId version上的唯一约束,则不会导致并发问题。不确定是否将聚合类型存储在事件存储中是一个好主意,列为:“aggregateId,version,eventData”,这取决于您的设计。在矿山设计中,相同的ID值可以由不同的聚合类型共享。@SHM我编写了聚合类型非事件type@SHM如果您使用的是eventstore.org,那么您是安全的;这些家伙保护您的事件流不受并发问题的影响;您可以在这里阅读更多内容:我想您谈论的是版本控制模型。。而不是序列本身的版本。我想op是在问测序。我遇到了类似的问题。我的缓存检查ES中的最新版本。。如果不一样,我会转储缓存,然后重新加载聚合并将其存储在缓存中。。而不是序列本身的版本。我想op是在问测序。我遇到了类似的问题。我的缓存检查ES中的最新版本。。如果不一样,我转储缓存,然后重新加载聚合并将其存储在缓存中。