事件溯源聚合中的版本号?

Imr*_*had 7 events design-patterns cqrs event-sourcing microservices

我正在构建微服务。我的微服务之一是使用 CQRS 和事件源。集成事件在系统中引发,我将聚合保存在事件存储中,同时更新我的​​读取模型。

我的问题是,当我们针对该聚合更新事件流时,为什么需要聚合版本?我读到我们需要这个以保持一致性,并且要按顺序重播事件,我们需要在保存之前检查版本(https://blog.leifbattermann.de/2017/04/21/12-things-you-should-know- about-event-sourcing/)我仍然无法解决这个问题,因为事件是按顺序引发和保存的,所以我真的需要具体的例子来了解我们从版本中获得了什么好处以及为什么我们甚至需要它们。

非常感谢,

伊姆兰

Rom*_*min 12

让我描述一个聚合版本有用的情况:

在我们的reSove 框架中,聚合版本用于乐观并发控制。

我会通过例子来解释它。假设InventoryItem聚合接受命令AddItemsOrderItemsAddItems增加库存物品数量,OrderItems- 减少。假设您有一个InventoryItem包含一个事件的聚合 #123 -ITEMS_ADDED数量为 5。聚合 #123 说明库存中有 5 件商品。

因此,您的 UI 向用户显示有 5 件商品库存。用户 A 决定订购 3 件商品,用户 B - 4 件商品。两个OrderItems命令几乎同时发出,假设用户 A 先是几毫秒。

现在,如果您在内存中有一个聚合 #123 的单个实例,在单线程中,您没有问题 - 来自用户 A 的第一个命令将成功,将应用事件,状态说数量为 2,所以第二个命令来自用户 B 将失败。

在分布式或无服务器系统中,来自 A 和 B 的命令将在单独的进程中,如果我们不使用某些并发控制,这两个命令都会成功并使聚合进入错误状态。有几种方法可以做到这一点 - 悲观锁定、命令队列、聚合存储库或乐观锁定。

乐观锁似乎是最简单和最实用的解决方案:

我们说每个聚合都有一个版本 - 其流中的事件数。所以我们的聚合 #123 有版本 1。

当聚合发出事件时,此事件数据具有聚合版本。在我们的案例中,ITEMS_ORDERED来自用户 A 和 B 的事件将具有 2 的事件聚合版本。显然,聚合事件应该具有按顺序增加的版本。所以我们需要做的只是放置一个数据库约束,即元组{aggregateId, aggregateVersion}在写入事件存储时应该是唯一的。

让我们看看我们的示例如何在具有乐观并发控制的分布式系统中工作:

  • 用户 A 发出OrderItem聚合命令#123

  • 聚合 #123 从事件中恢复 {version 1, quantity 5}

  • 用户 BOrderItem为聚合 #123发出命令

  • Aggregate #123 的另一个实例从事件中恢复(版本 1,数量 5)

  • 用户 A 的聚合实例执行命令,它成功,事件ITEMS_ORDERED {aggregateId 123, version 2}被写入事件存储。

  • 用户 B 的聚合实例执行一个命令,它成功了,ITEMS_ORDERED {aggregateId 123, version 2}它尝试将它写入事件存储并因并发异常而失败。

  • 在这样的异常命令处理程序上,用户 B 只是重复整个过程 - 然后聚合 #123 将处于 状态{version 2, quantity 2}并且命令将被正确执行。

我希望这可以清除聚合版本有用的情况。

  • 读取模型是从事件存储构建的,因此您只需在事件成功保存后应用事件,而不是在此之前。 (2认同)
  • 重试:除非您有一个每秒接收数千个命令的聚合(这将是一个设计缺陷),否则并发异常非常罕见。在我的示例中,用户 B 应该在用户 A 之后、用户 A 的事件保存之前发送命令 - 在几毫秒内。因此,如果 1-2 次重试没有帮助 - 您的设计中可能存在错误。 (2认同)