事件溯源和乐观并发控制

22

当你想让代码在竞态条件下运行时,常见的做法是使用乐观并发控制(OCC)。根据维基百科:

...在提交之前,每个事务都会验证没有其他事务修改了它读取的数据。如果检查发现有冲突的修改,提交事务将回滚...

一种实现OCC的方法是检查待修改数据的版本。如果版本不同,则表示其他事务已经修改了该数据,应用程序需要决定如何解决冲突(重新尝试、通知用户...)。

一个草案如下:

class Repository
{
    public class save($data)
    {
        $currentVersion = $data->version;
        $data->version  = $currentVersion + 1;

        $result = $this->db->update($data, [
            'id'        => $data->id,
            'version'   => $currentVersion
        ]);

        if (1 === $result) {
            // everything ok
        } else {
            // conflict!
        }
    }
}

我的问题是,在 EventSourcing 中,我们只会追加发生在领域中的所有事件,因此我们不能再使用这种方法来实现 OCC。还有哪些方法可以在使用 EventSourcing 的同时保持 OCC?

一种可能的选择是在存储事件时查找冲突的事件。这种方法允许对事件进行精细的控制。我不知道这是否会使解决方案过于复杂,或者这是一个被指出的“标准”,可以在http://danielwhittaker.me/2014/09/29/handling-concurrency-issues-cqrs-event-sourced-system/找到。

非常感谢你的帮助,如果对问题描述有任何疏漏之处,请指出。谢谢!

3个回答

14

在尝试将事件附加到流时,您可以指定一个期望的当前版本号,如果实际的当前版本号与之不匹配,则会抛出异常。

这种乐观并发机制已经内置于某些事件存储系统中,详情请查看文档

您提供的文章似乎描述了类似的方法,但更加强大,因为您可以访问自期望版本以来发生的事件类型,并且可以根据更细粒度的标准检测冲突。


6
默认情况下,乐观锁不适用于事件溯源,因为乐观锁需要锁定一个状态。在事件溯源中,您没有任何类似状态的内容,只有事件列表(更改的流)。
正如之前已经提到过的,您可以使用以下方法:
  1. 每个事件都有修订号码(在事件溯源方法中相当普遍)
  2. 每次收到任何事件并需要保存它时,您应该从事件存储中获取最后的修订号码
  3. 在将其保存在事件存储中之前,仅增加此修订号码
  4. 确保在事件存储中具有唯一索引的修订号码
  5. 如果两个并行事务增加了最后的修订号码(例如5) 并尝试保存两个具有增加的修订号码的新事件(在我们的示例中,它将是6),则其中一个将由于唯一索引而失败。
请还要记住,您的事件存储库应允许您使用唯一索引,在这种情况下,关系型数据库表是最佳选择。

对于第4点和第5点,当您只有一种类型的聚合时,在事件存储中您只能为修订号拥有唯一索引? - XuDing
你在谈论事件部分。但是命令方面会发生什么呢?在处理命令之前,你必须确保它符合所有不变量,并且这确实需要一个状态。根据该状态,命令可以被执行并生成一个或多个事件...或者出现错误。因此,拥有这种机制非常重要。因为你可以将不变量检查应用于一个经过变异但不满足所有要求的版本。所以你需要发出一个补偿消息或以其他方式处理它... - Gonzalo Aguilar Delgado

5

使用事件溯源技术时,您的表中还应该有一个版本字段,否则在构建聚合根时无法获取事件的顺序。但顺序很重要。您还可以利用此版本字段支持OCC。例如,如果您遇到竞态条件,如在事件存储中几乎同时保存具有相同ID和版本的两个事件,则最后一个将失败,并且如果您使用由聚合ID和版本组成的复合主键,则会引发重复键异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接