如何使用散点/聚集模式在MassTransit 3.0

时间:2015-11-07 05:39:03

标签: c# masstransit saga

Jimmy Boagard描述麦当劳快餐连锁店here将其与scatter gather pattern.进行比较

上述文章中隐藏的工作流图片:enter image description here

初步实施思路:

为所有食品加工站获得的所有类型的FoodOrdered事件建立一个通用界面,然后每个食品站将能够消费/创建其各自的项目并发布共同的完成事件。例如:薯条和汉堡站收到关于薯条顺序的消息,薯条站消耗该命令宣布该传奇正在监听的ItemDoneEvent。

最初的担忧:

由于佐贺并不关心所完成的食物类型,所有食物都已完成,这似乎是一个 OK 解决方案。但是之后阅读有关队列共享的警告here并注意到Consumer.Conditional filtering has been removed with MassTransit 3.0感觉好像框架正在说" Bad Things(TM)将会发生"用这种方法。但是我不确定如何在厨房中为每个食品项目创建消息请求和响应以及关联事件时如何做到这一点。例如:FriesOrdered,BurgerOrdered FriesCooked,BurgerCooked。如果您必须为厨房中的每件物品做这件事,这将非常繁琐?

考虑到上述问题 - 这类工作流的好例子是什么样的?

3 个答案:

答案 0 :(得分:1)

难道你不能“简单地”将对象作为事件参数传递到队列中吗? 当saga监听器获得“订单已完成”事件时,它将包含事件中完成的对象吗?

我想它是通过Generic方法发送到队列的,其中对象必须实现IFoodOrdered

然后你可以在实现一个虚拟方法,saga可以用它来拾取“泛型”东西,你只需要为那些特殊项目实现重载,这需要特殊的东西发生?

答案 1 :(得分:0)

将完成的事件踢回传奇的问题是它会在共享资源(即传奇状态)上产生争用。

Jim在您引用的那篇文章之后发布了另一篇文章,其中概述了问题和解决方案。当然,他专门谈论NServiceBus,但问题和概念是一样的。

https://lostechies.com/jimmybogard/2014/02/27/reducing-nservicebus-saga-load/

创建外部存储。为每个工作项记录一份记录。让每个工作人员设置自己的工作完成,而传奇有效地使用延迟消息进行民意调查,看看是否所有工作都已完成。

然后你仍在进行分散聚集,但是"聚合器"已被流程管理器模式取代,以减少争用。

答案 2 :(得分:0)

我遇到了类似的问题 - 需要发布一些命令(所有相同的接口,IMyRequest)并等待所有。

实际上我的命令启动了其他传奇,它在处理结束时发布IMyRequestDone而没有标记传奇完成。 (需要在以后的某个时间完成它们。)因此,我只是查询子传奇实例的状态,而不是在父传奇中保存已完成的嵌套传奇的数量。

检查每条MyRequestDone条消息:

Schedule(() => FailSagaOnRequestsTimeout, x => x.CheckToken, x =>
{
    // timeout for all requests
    x.Delay = TimeSpan.FromMinutes(10);
    x.Received = e => e.CorrelateById(context => context.Message.CorrelationId);
});


During(Active,
    When(Xxx)
        .ThenAsync(async context =>
        {
            await context.Publish(context => new MyRequestCommand(context.Instance, "foo"));
            await context.Publish(context => new MyRequestCommand(context.Instance, "bar"));

            context.Instance.WaitingMyResponsesTimeoutedAt = DateTime.UtcNow + FailSagaOnRequestsTimeout.Delay;
            context.Instance.WaitingMyResponsesCount = 2;
        })
        .TransitionTo(WaitingMyResponses)
        .Schedule(FailSagaOnRequestsTimeout, context => new FailSagaCommand(context.Instance))
    );

During(WaitingMyResponses,
    When(MyRequestDone)
        .Then(context =>
        {
            if (context.Instance.WaitingMyResponsesTimeoutedAt < DateTime.UtcNow)
                throw new TimeoutException();
        })
        .If(context =>
        {
            var db = serviceProvider.GetRequiredService<DbContext>();
            var requestsStates = db.MyRequestStates.Where(x => x.ParentSagaId == context.Instance.CorrelationId).Select(x => x.State).ToList();
            var allDone = requestsStates.Count == context.Instance.WaitingMyResponsesCount &&
                requestsStates.All(x => x != nameof(MyRequestStateMachine.Processing)); // assume 3 states of request - Processing, Done and Failed
            return allDone;
        }, x => x
            .Unschedule(FailSagaOnRequestsTimeout)
            .TransitionTo(Active))
        )
        .Catch<TimeoutException>(x => x.TransitionTo(Failed))
);

During(WaitingMyResponses,
    When(FailSagaOnRequestsTimeout.Received)
        .TransitionTo(Failed)

定期检查所有请求是否完成(通过&#34;减少NServiceBus Saga加载&#34;):

Schedule(() => CheckAllRequestsDone, x => x.CheckToken, x =>
{
    // check interval
    x.Delay = TimeSpan.FromSeconds(15);
    x.Received = e => e.CorrelateById(context => context.Message.CorrelationId);
});

During(Active,
    When(Xxx)
        .ThenAsync(async context =>
        {
            await context.Publish(context => new MyRequestCommand(context.Instance, "foo"));
            await context.Publish(context => new MyRequestCommand(context.Instance, "bar"));

            context.Instance.WaitingMyResponsesTimeoutedAt = DateTime.UtcNow.AddMinutes(10);
            context.Instance.WaitingMyResponsesCount = 2;
        })
        .TransitionTo(WaitingMyResponses)
        .Schedule(CheckAllRequestsDone, context => new CheckAllRequestsDoneCommand(context.Instance))
    );

During(WaitingMyResponses,
    When(CheckAllRequestsDone.Recieved)
        .Then(context =>
        {
            var db = serviceProvider.GetRequiredService<DbContext>();
            var requestsStates = db.MyRequestStates.Where(x => x.ParentSagaId == context.Instance.CorrelationId).Select(x => x.State).ToList();
            var allDone = requestsStates.Count == context.Instance.WaitingMyResponsesCount &&
                requestsStates.All(x => x != nameof(MyRequestStateMachine.Processing));
            if (!allDone)           
            {
                if (context.Instance.WaitingMyResponsesTimeoutedAt < DateTime.UtcNow + CheckAllRequestsDone.Delay)              
                    throw new TimeoutException();
                throw new NotAllDoneException();
            }
        })
        .TransitionTo(Active)
        .Catch<NotAllDoneException>(x => x.Schedule(CheckAllRequestsDone, context => new CheckAllRequestsDoneCommand(context.Instance)))
        .Catch<TimeoutException>(x => x.TransitionTo(Failed));