【问题标题】:Where to invoke SagaManager in CQRS even handling在 CQRS 甚至处理中调用 SagaManager 的位置
【发布时间】:2016-08-19 02:54:20
【问题描述】:

刚接触微服务和 CQRS 事件处理。我试图通过一项简单的任务来理解。在这个任务中,我有三个 REST 外部服务来处理一个事务/请求(服务)。这三个服务是

第一步:客户创建。

第二步:为客户创建业务

第三步:为企业创建地址。

我想使用 InMemorySagaRepository 和 saga 管理器为这些事件实现 SAGA。

我必须在哪里启动 SagaManager 与存储库,是在 RestController 还是在 CommandHandler ?

你能帮我理解 sagas flow 吗?

提前致谢。

【问题讨论】:

  • 欢迎来到 Stack Overflow。你可以改进你的问题。请阅读How to Ask,包括“如何以聪明的方式提问”链接。
  • 我们在工作中使用事件溯源和 CQRS。我们发现“传奇”对不同的人意味着不同的事物。所以我只想澄清一下:you 所说的 saga 是什么意思?一个指向您阅读它的链接会很有帮助,这样我们就知道您在问什么。

标签: cqrs saga


【解决方案1】:

半年后,我正在进行编辑,因为我现在参加了 Greg Young 举办的一门名为 Greg Young 的 CQRS、领域事件、事件溯源以及如何应用 DDD 的课程 我真的向任何考虑 CQRS 的人推荐它。帮助很多人了解实际情况


原始答案

在我们的产品中,我们使用 Sagas 作为对事件做出反应的东西。 这意味着我们的 sagas 实际上只是特定事件的订阅者。传奇然后包含一些关于它是否应该做某事的逻辑。 如果 saga 发现应该采取行动,它会创建一个命令并将其放在 CommandBus 上。 这意味着 Sagas 只是“反应器”并使用与用户相同的路径(跳过 API 等)。

但真正的 Saga 是什么,它应该做什么,与谈论它们的人不同。 (免责声明:这就是我阅读这些帖子的方式,它们实际上可能都说同样的话,但以一种让我 [+我的团队] 看到这一点的蓬松方式)

例如,http://blog.jonathanoliver.com/cqrs-sagas-with-event-sourcing-part-i-of-ii/ 提出了 Sagas 不应包含“业务逻辑”的观点(根据帖子,任何包含“如果”的内容都是业务逻辑)。

https://msdn.microsoft.com/en-us/library/jj591569.aspx 将 Sagas 视为协调不同聚合之间的事情的“流程管理器”(请记住,Aggregate1 不能直接与 Aggregat2 对话,因此需要一个“流程管理器”来协调通信)。简单来说:Event -> Saga -> Command -> Event -> Saga...到达最终目的地。

https://lostechies.com/jimmybogard/2013/03/21/saga-implementation-patterns-variations/ 谈到了 Saga 的两种不同模式。一个是“发布收集器”,它基本上根据命令协调应该发生的事情。另一个是'Reporter',它只是将事物的状态报告到他们需要去的地方。它不协调事物,它只是报告它需要报告的任何内容。

http://kellabyte.com/2012/05/30/clarifying-the-saga-pattern/ 有一篇关于 Saga 模式“是什么”的文章。声称 Sagas 应该/可以补偿不同的工作流程中断。

http://cqrs.nu/Faq/sagas 对 Sagas 有一个非常简短的描述,基本上说“它们是让聚合对其他聚合做出反应的状态机”。

那么,鉴于此,您真正希望 Saga 做什么?它应该协调一切吗?或者它应该只是做出反应而不关心聚合做什么?


我编辑的部分

因此,在参加了 CQRS 课程并与 Greg 讨论了这一点之后,我得出的结论是,网络上有很多混乱。

让我们从“Saga”这个概念开始。 Saga 实际上与 CQRS 无关。这不是它的概念。 'Saga' 一种两阶段提交的形式,只是针对成功而不是失败进行了优化(https://en.wikipedia.org/wiki/Compensating_transaction

现在,大多数人在谈论 CQRS 并说“Saga”时的意思是“流程管理器”。而且流程管理器看起来相当复杂(Greg 有一门专门针对流程管理器的课程)。 基本上他们所做的是管理某事的整个过程(顾名思义)。与 Microsoft 的链接几乎就是它的全部内容。

回答问题:

我必须在哪里使用存储库启动 SagaManager,是在 RestController 还是在 CommandHandler 中?

在他们之外。 流程管理器是它自己的东西。它跨越聚合和存储库。从概念上讲,最好将其视为用户做您希望 PM 做的所有事情,只是您对用户交互进行编程并告诉它要听什么。

免责声明:我不为 Greg 工作,也不为任何因参加他的课程而获得晋升的人工作。只是我从中学到了很多,所以我推荐它,就像我推荐阅读 Eric Evans 关于 DDD 的书一样。

【讨论】:

  • 感谢您的快速回复。我的要求是协调服务/事件。
  • @user1386039 我认为您的意思是协调,因为它将与聚合的行为方式错综复杂地相关。作为另一个选项('reactor')并不真正关心聚合的方式。你想让传奇阻止事件的发生吗?这意味着在命令层中执行所有操作,或者拥有一个以某种方式阻止事件发布的 Saga。
  • @ShawnMclean 通过 SkillsMatter skillsmatter.com/courses/…完成的那一项@
【解决方案2】:

在我的应用程序中,我使用this MSDN 文档构建了 Saga 流程管理器,我的 Saga 在应用程序服务层中实现,它侦听销售事件、仓库和计费有界上下文,并在事件发生时通过服务总线发送命令。

简单的例子,希望它可以帮助你分析如何构建你的saga(我在Composition Root注册saga作为handler);):

传奇:

    public class SalesSaga : Saga<SalesSagaData>,
    ISagaStartedBy<OrderPlaced>,
    IMessageHandler<StockReserved>,
    IMessageHandler<PaymentAccepted>
{
    private readonly ISagaPersister storage;
    private readonly IBus bus;

    public SalesSaga(ISagaPersister storage, IBus bus)
    {
        this.storage = storage;
        this.bus = bus;
    }

    public void Handle(OrderPlaced message)
    {
        // Send ReserveStock command
        // Save SalesSagaData
    }

    public void Handle(StockReserved message)
    {
        // Restore & Update SalesSagaData
        // Send BillCustomer command
        // Save SalesSagaData
    }
    public void Handle(PaymentAccepted message)
    {
        // Restore & Update SalesSagaData
        // Send AcceptOrder command
        // Complete Saga (Dispose SalesSagaData)
    }
}

InMemorySagaPersister:(作为 SalesSagaDataID,我使用 OrderID,它在整个过程中是唯一的)

    public sealed class InMemorySagaPersister : ISagaPersister
{
    private static readonly Lazy<InMemorySagaPersister> instance = new Lazy<InMemorySagaPersister>(() => new InMemorySagaPersister());

    private InMemorySagaPersister()
    {
    }

    public static InMemorySagaPersister Instance
    {
        get
        {
            return instance.Value;
        }
    }

    ConcurrentDictionary<int, ISagaData> data = new ConcurrentDictionary<int, ISagaData>();

    public T GetByID<T>(int id) where T : ISagaData
    {
        T value;
        var tData = new ConcurrentDictionary<int, T>(data.Where(c => c.Value.GetType() == typeof(T))
            .Select(c => new KeyValuePair<int, T>(c.Key, (T)c.Value))
            .ToArray());

        tData.TryGetValue(id, out value);
        return value;
    }

    public bool Save(ISagaData sagaData)
    {
        bool result;

        ISagaData existingValue;
        data.TryGetValue(sagaData.Id, out existingValue);
        if (existingValue == null)
            result = data.TryAdd(sagaData.Id, sagaData);
        else
            result = data.TryUpdate(sagaData.Id, sagaData, existingValue);

        return result;
    }

    public bool Complete(ISagaData sagaData)
    {
        ISagaData existingValue;
        return data.TryRemove(sagaData.Id, out existingValue);
    }
}

【讨论】:

    【解决方案3】:

    一种方法可能是使用某种启动命令来启动 Saga。在这种情况下,它将在您的组合根目录中配置为侦听某个命令类型。一旦在您的消息调度程序(或您拥有的任何中间件消息传递内容)中收到命令,它就会查找已注册以由该命令启动的任何 Sagas。然后,您将创建 Saga 并将命令传递给它。然后,它可以对发生的其他命令和事件做出反应。

    在您的场景中,我建议您的 Saga 一种命令处理程序,因此它会在收到命令时启动

    【讨论】:

    • 感谢您的快速回复。我的主要服务是调用其他三个服务。如果没有错,我必须在主要服务中启动我的传奇。
    猜你喜欢
    • 2020-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多