How to implement a saga using a scatter/Gather pattern In MassTransit 3.0

后端 未结 3 1470
既然无缘
既然无缘 2021-02-03 18:00

Jimmy Boagard describes a McDonalds fast food chain here comparing it to a scatter gather pattern.

Workflow image stolen from above article:

Initial Im

3条回答
  •  抹茶落季
    2021-02-03 18:54

    I came into similar problem - need to publish few dozends of commands (all same interface, IMyRequest) and wait all.

    Actually my command initiates other saga, which publish IMyRequestDone at the end of processing without marking saga completed. (Need to complete them at some time later.) So instead of saving number of completed nested sagas in parent saga I just query state of child saga instances.

    Check on every MyRequestDone message:

    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();
                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(x => x.TransitionTo(Failed))
    );
    
    During(WaitingMyResponses,
        When(FailSagaOnRequestsTimeout.Received)
            .TransitionTo(Failed)
    

    Periodically check that all requests done (by "Reducing NServiceBus Saga load"):

    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();
                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(x => x.Schedule(CheckAllRequestsDone, context => new CheckAllRequestsDoneCommand(context.Instance)))
            .Catch(x => x.TransitionTo(Failed));
    

提交回复
热议问题