CQRS存储库/事件发布者

时间:2015-11-06 14:56:14

标签: events domain-driven-design cqrs event-sourcing aggregateroot

我正在使用CqrsLite进行CQRS式项目。具体Repository实现的Save方法如此(省略了不相关的行)。

    public void Save<T>(T aggregate, int? expectedVersion = null) where T : AggregateRoot
    {
        if (expectedVersion != null && _eventStore.Get(typeof(T), aggregate.Id, expectedVersion.Value).Any())
            throw new ConcurrencyException(aggregate.Id);

        var i = 0;
        foreach (var @event in aggregate.GetUncommittedChanges())
        {
            // ... [irrelevant code removed] ...
            _eventStore.Save(typeof(T), @event);
            _publisher.Publish(@event);
        }
        aggregate.MarkChangesAsCommitted();
    }

令我不安的是,这个方法是在聚合被告知将它们标记为已提交之前提交要发布给订阅者的事件。因此,如果观察给定事件的事件处理程序会阻塞,则聚合将不会提交已通知先前事件处理程序的更改。

为什么我不会在 aggregate.MarkChangesAsCommitted()之后将_publisher.Publish(@event)移动到,就像这样。我错过了什么?

    public void Save<T>(T aggregate, int? expectedVersion = null) where T : AggregateRoot
    {
        if (expectedVersion != null && _eventStore.Get(typeof(T), aggregate.Id, expectedVersion.Value).Any())
            throw new ConcurrencyException(aggregate.Id);

        var events = aggregate.GetUncommittedChanges();
        foreach (var @event in events)
        {
            // ... [irrelevant code removed] ...
            _eventStore.Save(typeof(T), @event);
        }
        aggregate.MarkChangesAsCommitted();
        _publisher.Publish(events);
    }

3 个答案:

答案 0 :(得分:8)

这两种方法都存在问题,因为SavePublish之间可能存在错误,无论调用这两种方法的顺序如何。这可能导致未发布的事件被发布或保存的事件未被发布。存在内存状态损坏(在聚合对象中)的问题也存在(尽管可以通过简单地捕获由事件处理程序产生的错误来处理)。

此问题的一个解决方案是使用两阶段提交(例如,如果您的事件存储是基于SQL Server且发布者是基于MSMQ的,则可用)。但是,这具有性能,可伸缩性和操作含义,并且不允许后期订阅者(见下文)。

更好的方法是允许对事件感兴趣的各方拉出事件存储区(理想情况下,将其与某种通知机制相结合或长时间轮询以使其更具“反应性”) 。这将跟踪最后收到的事件的责任转移到订户,允许

  • 迟到的订阅者(在存储事件后很长时间加入)接收旧事件以及新事件,
  • 没有两阶段提交的可靠性。

在搜索“使用事件存储作为队列”之类的内容时,您应该找到更多有关此方法的信息,而Greg的答案中的视频也可能会为此添加很多内容。

这是一个常见的算法:

  1. 事件存储为每个保存的事件分配一个检查点令牌(例如,序列号);
  2. 订阅者向事件存储区询问新事件(定期,基于长轮询,对推送通知作出反应等),从他们知道的最后一个检查点令牌(如果有)开始,
  3. 事件存储从该检查点令牌开始发送较新的事件以及新的检查点令牌
  4. 订阅者处理事件,并且如果可能的话,以原子方式存储新的检查点令牌以及它们产生的任何副作用;
    • 如果无法进行原子保存,他们可以在产生副作用后存储新的检查点令牌他们需要一种方法来忽略他们已经看到的事件,以防出现错误 - 之间(事件处理被称为“幂等”);
  5. 订阅者再次从#2开始。
  6. 我想补充一点,我不认为忽略Save / Publish问题生产就绪的事件存储。有关替代方案,请参阅Greg Young's Event Store或(目前或多或少未维护)NEventStore

答案 1 :(得分:1)

无论您选择CommitPublish的哪个订单,如果第二个订单失败,您都会遇到问题。

有多种方法可以解决此问题。以下是您的主要选择:

  1. 使用与应用程序使用相同数据库的消息传递基础结构,以便可以使用单个事务。当一个非常简单的消息传递基础设施足够时,该解决方案是可行的,并且团队决定自己构建它。

  2. 使用2阶段提交。这会对性能产生影响,这可能与您的应用程序相关,也可能不相关。

  3. 手动确保您不会陷入不一致的状态。有关详细信息,请参阅this answer of mine,我认为复制整个答案并不合理。

答案 2 :(得分:-4)

这是一种反模式,你不应该这样做。

显然发布链接会阻止我以可疑请求登录&#34;

https://www.youtube.com/watch?v=GbM1ghLeweU