使用 JOliver EventStore 更新多个聚合

Posted

技术标签:

【中文标题】使用 JOliver EventStore 更新多个聚合【英文标题】:Updating multiple aggregates using JOliver EventStore 【发布时间】:2011-11-15 21:12:07 【问题描述】:

我有一个关于使用JOliver's Event Store 在单个事务中更新多个聚合的问题。据我了解,每个聚合都应该有自己的事件流。现在,虽然许多命令处理程序只会加载单个聚合并只更新该聚合(即为这些聚合保存事件),但我可以想象会有需要更新多个聚合的命令处理程序。当然,我想以交易的方式做到这一点。

但是,我不知道如何使用 Event Store 做到这一点。通过在事件流上调用 CommitChanges() 来存储事件。如果我们有多个要更新的聚合,将有多个事件流,因此会多次调用CommitChanges()。使该事务成为事务的唯一方法是将其包装在 TransactionScope 中,但这没有多大意义,因为底层存储技术可能不支持事务。所以我最终得到了这段代码,这绝对不是我想要的:

        Guid aggregateGuid1 = Guid.NewGuid();
        Guid aggregateGuid2 = Guid.NewGuid();
        Guid commitGuid = Guid.NewGuid();

        var stream = store.OpenStream(aggregateGuid1, 0, int.MaxValue);
        stream.Add(new EventMessage()  Body = new MonitorDisabled  MonitorGuid = aggregateGuid1, User = "A"  );
        stream.CommitChanges(commitGuid);

        stream = store.OpenStream(aggregateGuid2, 0, int.MaxValue);
        stream.Add(new EventMessage()  Body = new MonitorEnabled  MonitorGuid = aggregateGuid2, User = "B"  );
        // Can't commit twice with the same commit id, what if fails after first one? No way for the store to know it had to write the second part of the commit.
        stream.CommitChanges(commitGuid);

这让我觉得我完全错过了应该如何使用 Event Store 的一些东西。有人可以帮我吗?非常感谢!

【问题讨论】:

【参考方案1】:

聚合定义事务边界。

如果您需要执行跨聚合交易,您应该检查您的聚合并重新设计它们。

如果一个操作 ( command ) 影响多个聚合,并且您确信您的聚合设计良好并且映射到您域中的实际一致性边界,eventual consitency 可能就是您要寻找的。只需向每个聚合发送一个命令,并有两个事务,每个事务一个。如果你觉得最终的一致性不适合你的情况,恐怕它又回到了绘图板上。

【讨论】:

嗯,举个经典的例子:银行交易。一个帐户是一个集合,我必须将钱从一个帐户转移到另一个帐户。这意味着我必须在一个帐户上存储像 MoneySent 这样的事件,在另一个帐户上存储事件 MoneyReceived。这对我来说是一个对 2 个聚合进行事务更新的合法场景。我错过了什么? 更多阅读告诉我这种情况应该使用补偿动作来实现。我将不得不对此进行更多阅读。能够在 2 个聚合上编写单个事务似乎是一个更简洁的解决方案(当您控制域中的两个帐户时)。 使用 DDD 和 ES/CQRS,你会得到一些东西,也会失去一些东西。这是CAP Theorem 你获得了分区、可伸缩性、高可用性等,但你失去了一致性。好吧,您实际上并没有丢失它,只是延迟了它。假设收益超过了损失,您就只能为您描述的情况寻找解决方案,并且您已经发现通常有一些普遍接受的解决方案,例如补偿行动。 另一件事是,您最好确保收益大于损失:构建一个可用于少于 100 人的高度可扩展系统是没有意义的。【参考方案2】:

我不能代表 John Oliver,但我认为答案是“不要”。聚合是事务边界。如果您需要协调多个聚合的提交,则需要一个显式的协调过程,例如 saga,这将完成并在必要时撤消相关事件。

【讨论】:

现在,假设我们从事托管业务,并且我们正在管理数据中心机架中的房间中的服务器。每个服务器都由一组监视器监控,我们称之为监视器配置。现在,我们要在维护期间禁用监视器。而且,我们可以对服务器、机架、房间或完整的数据中心进行维护。监视器配置是服务器监视器的自然聚合,当我们将机架进行维护时,我们希望在监视器配置聚合上引发 MonitorDisabled 事件。我们在哪里定义 TX 边界?服务器、机架、房间……? 如果你有跨越聚合边界的转录边界,那么你做错了。根据定义,聚合是事务边界,您的模型应该反映这一点。【参考方案3】:

如果您的基础架构支持,有时在这些场景中利用分布式事务会更容易。

带有 EventStore 的 TransactionScope:

EventStore 默认禁止创建任何环境事务 在提交对数据库的更改之前由 NServiceBus 执行。然而,如果 您正在使用支持分布式的队列和数据库 事务(MSMQ、SQL Server、Raven 等)然后您可以更改 EventStore 的 TransactionScopeOption 为必需。这将确保 EventStore 在环境事务中登记,其中将 使用 MSDTC 和消息队列和数据库变得分布式 将保持同步。 - NES Documentation

使用 RavenDB 进行跨文档事务:

你会从我死去、冰冷、受伤的手中撬开交易 - Ayende

【讨论】:

以上是关于使用 JOliver EventStore 更新多个聚合的主要内容,如果未能解决你的问题,请参考以下文章

从 eventStore 获取特定数量的事件

使用EventStore,我可以创建一个新的iCal日历类型吗?

使用 Kafka 作为 EventStore 时在 Flink 中恢复状态一致性

Prooph Eventstore (PDO) 和 Doctrine DBAL 导致多个连接

分享一个CQRS/ES架构中基于写文件的EventStore的设计思路

Event Store 2.0发布,带来了安全支持和测试版Projections库