我们使用的是CQRS+ES。ES是NEventStore(后面是JOliver EventStore)。我们在不同的命令中有两个聚合。第二AR的投影取决于由读取模型中的第一AR投影写入的数据。问题是,当我们运行软件时,一切都进行得太快了,以至于有时这两个聚合会以相同的日期时间(列CommitStamp)持久化在事件存储中。当重放事件时,我们从一开始就按照CommitStamp列的顺序获取它们。但是,如果这两个流具有相同的CommitStamp并且以错误的顺序获取,则读取的模型投影会例外。
知道如何解决这个问题吗?
====================================
以下是github上关于这个问题的讨论https://github.com/NEventStore/NEventStore/issues/170
====================================
编辑:这就是我们当前重放事件的方式。我搜索了GetFrom(…)的工作原理,结果发现commitstamp列不用于排序。毕竟没有提交订单。所以,如果我开始回放事件,它可能会从今天开始返回一个事件,下一个是2年前记录的事件,下个等等
public void ReplayEvents(Action<List<UncommittedEvent>> whatToDoWithEvents, DateTime loadEventsAfterDate)
{
var eventPortion = store.Advanced.GetFrom(loadEventsAfterDate);
var uncommitedEventStream = new UncommittedEventStream();
foreach (var commit in eventPortion)
{
foreach (var eventMessage in commit.Events.ToList()))
{
uncommitedEventStream.Append(new UncommittedEvent(eventMessage.Body));
}
}
whatToDoWithEvents(uncommitedEventStream.ToList());
}
在NEventStore中,一致性边界是流。从3.2版开始(正如@Marijn提到的,问题#159),CommitSequence列用于在从所有持久性引擎的流中读取时对CommitMessages(以及其中包含的EventMessages)进行排序。
EventMessage的排序是在每个流的基础上保证的。在流之间不存在消息的隐含顺序。由于所选持久性引擎的某些方面而可能发生的任何实际排序都是偶然的,不能依赖。
保证跨流排序将严重限制库的分布式友好方面。即使我们考虑这样一个功能,它也必须与所有支持的持久性引擎协同工作,其中包括NoSQL存储。
如果您正在练习域驱动设计,其中每个流代表一个聚合根,并且您需要保证在2个或多个聚合之间进行排序,这表明您的域模型中存在设计问题。
如果您的投影需要合并来自多个源(流)的值,您可以依赖于对源内进行排序,但在对源间进行排序时需要灵活。您还应该考虑到重复消息的可能性,尤其是当您通过外部总线或队列进行重放时。
如果您试图使用时间戳(CommitStamp)在接收方重新排序多个流,这将是脆弱的。时间戳具有固定的分辨率(毫秒、刻度等)。即使只有一个作家,事情也可能"同时"发生。
Damian在数据库中添加了一个检查点列。这是在当前主分支中。当用GetFromCheckpoint(int)
重放事件时,结果是正确的。
在数据库级别,虽然CommitStamp可以进行筛选,但CommitSequence列应该指导排序。
至于API对您正在使用的任何版本的libs的调用,我将把它留给您练习(或者如果您填写代码片段和/或提到版本,也许其他人可以介入)