在事件源环境中重新水合聚合根及其关联实体的最佳方法是什么

What is the best way to rehydrate aggregate roots and their associated entities in an event sourced environment

我在 SO 中看到了关于重新水化聚合根的信息,但我发布这个问题是因为我没有在 SO 中找到任何关于在事件源框架的上下文中这样做的信息。

在使用事件源和 CQRS 模式在应用程序的命令端操作时,是否发现或开发了如何重新水化聚合根的最佳实践

或者这是否更像是建筑师的“偏好”?

我已经阅读了很多博客,在你的管上看了很多会议演示,我似乎得到了不同的指导,这取决于我参加的对象。

一方面,我发现信息相当清楚地表明开发人员应该创建聚合以对直接从事件存储中获取的事件使用“apply”方法来滋润自己。

另一方面,我也在几个地方看到演示者和博主建议通过向应用程序的读取端提交查询来重新水合聚合根。一些人建议在读取端创建特定的验证“桶”/投影以促进这一点。

任何人都可以帮助我指出正确的方向,以发现是否存在单一的最佳实践,或者答案是否主要取决于性能问题或我没有考虑的其他问题?

事件源框架中的聚合聚合是一个很好理解的问题。

On the one hand, I have found information stating fairly clearly that developers should create aggregates to hydrate themselves using “apply“ methods on events obtained directly from the event store..

这是规定的处理方式。有多种方法可以实现这一点,但我建议将任何持久性逻辑(读取或写入事件)保留在聚合之外。一种简单的方法是公开接受域事件然后应用这些事件的构造函数。

On the other hand, I have also seen in several places where presenters and bloggers have recommended rehydrating aggregate roots by submitting a query to the read side of the application. Some have suggested creating specific validation “buckets“ / projections on the read side to facilitate this.

您可以使用快照的概念来优化读取。这将创建你的水合聚合的记忆版本。您可以加载此快照,然后仅应用自快照创建以来生成的事件。在这种情况下,您的聚合可以定义一个带有两个参数的构造函数:一个现有状态(快照)和任何剩余的域事件,然后可以应用于该快照。

快照只是一种优化,应该这样考虑。您可以创建一个不使用快照的系统,并在读取性能成为瓶颈时应用它们。

On the other hand, I have also seen in several places where presenters and bloggers have recommended rehydrating aggregate roots by submitting a query to the read side of the application

快照实际上并不是应用程序读取端的一部分。读取端的数据是为了满足应用程序中的用例而存在的。即使底层域没有改变,这些也可以根据需求改变。因此,您根本不应该在您的域中使用读取端数据。

多年来,事件溯源发展出了不同的风格。我可以将所有这些分为两大类:

  • 一个事件流代表一个实体(在 DDD 的情况下是一个聚合)
  • (子)系统的一个(分区)事件流

当您为每个(子)系统处理一个流时,您无法动态地重新水化写入端,由于该流中的事件数量,这在物理上是不可能的。因此,您将依赖投影读取端来检索当前实体状态。因此,这个读取端必须完全一致。

在使用 DDD 风格的事件溯源时,社区对应该如何完成达成了强烈的共识。聚合的状态(不仅仅是根,而是整个聚合)在调用域模型之前由命令端恢复。您总是使用事件进行恢复。启用快照后,快照也会作为事件存储在聚合快照流中,因此您可以从快照版本中读取最后一个事件和所有事件。

关于Apply的事情。您需要明确区分将新事件添加到更改列表(您要保存的内容)的功能和应用事件时改变聚合状态的功能。

第一个函数称为 Apply,第二个通常称为 When。因此,您在聚合代码中调用 Apply 函数来构建更改列表。 When 函数在您读取流时从事件恢复聚合状态时被调用,也从 Apply 函数调用。

您可以在我的书库中找到事件源聚合的简单示例:https://github.com/alexeyzimarev/ddd-book/blob/master/chapter13/src/Marketplace.Ads.Domain/ClassifiedAds/ClassifiedAd.cs

例如:

    public void Publish(UserId userId)
        => Apply(
            new V1.ClassifiedAdPublished
            {
                Id = Id,
                ApprovedBy = userId,
                OwnerId = OwnerId,
                PublishedAt = DateTimeOffset.Now
            }
        );

而对于 When

    protected override void When(object @event)
    {
        switch (@event)
        {
            // more code here

            case V1.ClassifiedAdPublished e:
                ApprovedBy = UserId.FromGuid(e.ApprovedBy);
                State = ClassifiedAdState.Active;
                break;

            // and more here
         }
     }