使用对多个客户端的乐观并发检查实现事件存储

Mar*_*edy 5 optimistic-concurrency ravendb event-sourcing

我正在尝试使用符合我所看到的各种 Greg Young 启发示例的实践和原则来实现一个事件溯源系统。

我了解版本检查逻辑是如何工作的,并且在保存聚合时,如果当前版本与预期版本不匹配,则意味着另一个会话/客户端应用程序在您之前更新了聚合。

我也知道您可以制定一种在保存并发事件时追溯解决冲突的方法,这个问题与这样做无关。

我想了解的是,在使用 nosql 数据库(例如 ravendb)作为事件存储的特定实现中,我将如何确保写入的事件不会因竞争条件而与版本号重叠。

以下代码来自一个示例项目来说明:

Repository<TAggregate>存储库类中有一个保存方法

    public void Save(AggregateRoot aggregate, int expectedVersion)
    {
        if (aggregate.GetUncommittedChanges().Any())
        {
            lock (_lockStorage)
            {
                var item = new T();

                if (expectedVersion != -1)
                {
//issue if two processes get to this line of code below together 
//and both have the same 'version to be expected' then start writing together
                    item = GetById(aggregate.Id); 
                    if (item.Version != expectedVersion)
                    {
                        throw new ConcurrencyException(string.Format("Aggregate {0} has been previously modified",
                                                                     item.Id));
                    }
                }

                _storage.Save(aggregate);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在,当只有一个应用程序时,这基本上可以正常工作。当当前线程获取锁、检查版本然后写入自己的事件时,锁会停止任何其他线程将事件写入事件存储。

然而,想象两个独立的客户端在不同的机器上运行。显然,这两个进程都可以一起进入锁,然后它们都可以执行该GetById()方法,并且都可以看到相同的当前提交的版本。然后,两个进程将继续写入具有递增版本号的未提交事件。但是,这会使聚合的事件流处于可能存在具有相同版本号的不同事件的状态。

我知道我可以做一些回顾性决议,但这不是我想要完成的。

现在很明显,这意味着需要在事件存储数据库方面进行某种锁定。谁能建议如何做到这一点?答案不一定是特定于数据库的,但是 ravendb 示例会很棒,因为这就是我计划用它来构建我的事件源系统原型的原因。

Cod*_*ler 1

在不同机器上运行的两个单独的客户端无法通过 API 与应用程序交互是否有任何原因?这样,事件的中央处理就可以在一台机器上进行,并避免您所描述的问题。如果您指的是部分连接的场景,那么您仍然不会在客户端上执行此逻辑。如果不可避免,您将需要在某个时刻组合事件流,然后处理冲突。

如果有帮助的话,我确实有一篇关于处理并发问题的帖子,但它不能处理您的确切场景,但可能会给您一些解决并发冲突的想法。