【问题标题】:Should an event-sourced aggregate root have access to the event sourcing repository?事件源聚合根是否应该有权访问事件源存储库?
【发布时间】:2010-05-25 16:07:35
【问题描述】:

我正在开发一个event-sourced CQRS 实现,在应用程序/域层中使用 DDD。我有一个看起来像这样的对象模型:

public class Person : AggregateRootBase
{
    private Guid? _bookingId;

    public Person(Identification identification)
    {
        Apply(new PersonCreatedEvent(identification));
    }

    public Booking CreateBooking() {
        // Enforce Person invariants
        var booking = new Booking();
        Apply(new PersonBookedEvent(booking.Id));
        return booking;
    }

    public void Release() {
        // Enforce Person invariants
        // Should we load the booking here from the aggregate repository?
        // We need to ensure that booking is released as well.
        var booking = BookingRepository.Load(_bookingId);
        booking.Release();
        Apply(new PersonReleasedEvent(_bookingId));
    }

    [EventHandler]
    public void Handle(PersonBookedEvent @event) { _bookingId = @event.BookingId; }

    [EventHandler]
    public void Handle(PersonReleasedEvent @event) { _bookingId = null; }
}

public class Booking : AggregateRootBase
{
    private DateTime _bookingDate;
    private DateTime? _releaseDate;

    public Booking()
    {
        //Enforce invariants
        Apply(new BookingCreatedEvent());
    }

    public void Release() 
    {
        //Enforce invariants
        Apply(new BookingReleasedEvent());
    }

    [EventHandler]
    public void Handle(BookingCreatedEvent @event) { _bookingDate = SystemTime.Now(); }
    [EventHandler]
    public void Handle(BookingReleasedEvent @event) { _releaseDate = SystemTime.Now(); }
    // Some other business activities unrelated to a person
}

根据我目前对 DDD 的理解,Person 和 Booking 都是单独的聚合根,原因有两个:

  1. 有时业务组件会从数据库中单独提取 Booking 对象。 (即,已获释的人之前的预订因信息不正确而被修改)。
  2. 当 Booking 需要更新时,Person 和 Booking 之间不应存在锁定争用。

另一个业务要求是一个人一次不能多次进行预订。因此,我担心在读取端查询查询数据库,因为那里可能存在一些不一致(由于使用 CQRS 并具有最终一致的读取数据库)。

是否应该允许聚合根通过 id 查询事件源后备存储中的对象(根据需要延迟加载它们)?还有其他更有意义的实施途径吗?

【问题讨论】:

  • " public Person(Identification Identification) { Apply(new PersonCreatedEvent(identification)); }" 如果你从内存中构建对象会发生什么,你不是在创建一个新人,但你会正在发布一个新事件。我喜欢你定义事件处理程序的方式。因此,知道对象的状态只能在将命令应用于存储库时更改,也许您也应该添加命令处理程序,除非您要完全基于事件,恕我直言,这没有多大意义。
  • 我不明白为什么您要处理您自己的存储库应该发布的事件? “BookingCreatedEvent”

标签: repository domain-driven-design cqrs


【解决方案1】:

首先,您真的需要事件溯源吗?对我来说这看起来很简单。事件溯源既有优点也有缺点。虽然它为您提供了免费的审计跟踪并使您的域模型更具表现力,但它使解决方案变得复杂。

好的,我假设此时您已经考虑过您的决定,并且您决心继续使用事件溯源。我认为您缺少将消息传递作为聚合之间通信手段的概念。它在Pat Helland's paper 中描述得最好(顺便说一句,不是关于 DDD 或事件溯源,而是关于可伸缩性)。

这个想法是聚合可以相互发送消息以强制执行某些行为。聚合之间不能有同步(也就是方法调用)交互,因为这会引入一致性问题。

在您的示例中,Person AR 会将 Reserve 消息发送到 Booking AR。该消息将以某种异步且可靠的方式传输。 Booking AR 会处理这个消息,如果它已经被另一个人预订,它会回复 ReservationRejected 消息。否则,它将发送 ReservationConfirmed。这些消息必须由 Person AR 处理。可能,他们会生成另一个事件,该事件将被转换为发送给客户的电子邮件或类似的东西。

无需在模型中获取查询数据。只是发消息。如果你想要一个例子,你可以下载Ncqrs 项目的“Messaging”分支的源代码并查看 ScenarioTest 类。它使用 Blue Book 中的 Cargo 和 HandlingEvent 示例演示了 AR 之间的消息传递。

这能回答你的问题吗?

【讨论】:

  • 如果强制同步处理会遇到什么样的一致性问题?对我来说,这似乎只是一个可扩展性的问题,但我可能会遗漏一些东西。
  • 我假设存储模型不支持存储跨聚合事务。如果它支持,那么正常的方法调用就可以了(至少就一致性而言)。
  • 看完 Pat Helland 的论文后,我明白了你的出处。在这个特定的系统中,我不关心近乎无限的可扩展性(尽管这将是一个很好的问题)。作为实现细节,我们使用 MSMQ。我相信使用 MSMQ 的 2PC 在我们的场景中是可以接受的。不过,我确实喜欢这个主意,并且让我深思。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多