0

I am new to microservices and using masstransit with automatonymous. Currently my state machine is showing inconsistency while execution. The code inside Initially works as expected but the control flow does not execute the code inside During after the second event has completed. This happens only for 1 state machine, all the other state machine works as expected. When I move the code inside During to Initially(commented code in initially), it works fine. Below is how my state machine looks like:

 InstanceState(x => x.CurrentState);
        Event(() => ServiceRequest1Registered, x => x.CorrelateById(context => context.Message.AggregateId));
        Event(() => ServiceRequest2Registered, x => x.CorrelateById(context => context.Message.AggregateId));

        Initially(
           When(ServiceRequest1Registered, 
           context => context.Data.ServiceTypeId != (int)ServiceType.IndividualService)
           .Then(context => _logger.LogInformation($"When Initially, ServiceRequest1Registered and wrong condition"))
           .Finalize(),
           When(ServiceRequest1Registered, 
           context => context.Data.ServiceTypeId == (int)ServiceType.IndividualService)
           .Then(context => _logger.LogInformation($"When Initially and ServiceRequest1Registered"))
           .Send(url,
               x => new ServiceRequest2RegisteredCommand
               {
                   InitiatedBy = x.Instance.InitiatedBy,
                   ServiceRequestId = x.Instance.Id,
                   Schedules = _mapper.Map<List<ScheduleDTO>>(x.Data.ServiceRequest1Schedules)
               })
           .Then(context => _logger.LogInformation($"Send ServiceRequest2RegisteredCommand")
           .TransitionTo(ServiceRequest1RegisterCompleted)
           ////When(ServiceRequest2Registered)
           ////.Then(context => _logger.LogInformation($"When ServiceRequestRegister2Completed and ServiceRequest2Registered"))
           //// .Finalize())
           );

        During(ServiceRequestRegister1Completed,
           Ignore(ServiceRequest1Registered),
           When(ServiceRequest2Registered)
           .Then(context => _logger.LogInformation($"When ServiceRequestRegister1Completed and ServiceRequest2Registered"))
            .Finalize());

        SetCompleted(async instance =>
        {
            State<ServiceRequestState> currentState = await this.GetState(instance);

            _logger.LogInformation($"Final state : {ServiceRequest2Registered.Equals(currentState)}");
            return ServiceRequest2Registered.Equals(currentState);
        });

Masstransit setup with RabbitMQ below

services.AddMassTransit(x =>
        {
            x.AddBus(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
            {
                cfg.Host(hostUri, hst =>
                {
                    hst.Username(appSettings.RabbitMQ.Username);
                    hst.Password(appSettings.RabbitMQ.Password);
                });

                cfg.ReceiveEndpoint("microservice-response", e =>
                {
                    e.UseInMemoryOutbox();
                    AddConsumers(e, provider);
                    e.ConfigureSaga<ServiceRequestRegisterState>(provider);
                });
            }));

            x.AddSagaStateMachine<ServiceRequestRegisterStateMachine, ServiceRequestRegisterState>()
               .InMemoryRepository();
        });

        services.AddSingleton<IHostedService, MassTransitBusService>();

I tried both SetCompleted and SetCompletedWhenFinalized(). We are using Masstransit v6.2.1 with automatonymous v4.2.1. Need help to identify why we have this issue or if there is something wrong with implementation?

Rahul Jacob
  • 19
  • 1
  • 5
  • Doesn't the first when unconditionally finalises the state machine? `When(ServiceRequest1Registered, context => context.Data.ServiceTypeId != (int)ServiceType.IndividualService).Then(context => _logger.LogInformation($"When Initially, ServiceRequest1Registered and wrong condition")).Finalize(),` – Alexey Zimarev Oct 25 '20 at 18:23
  • @AlexeyZimarev, thanks for the comment. Correct me if I am wrong but my understanding was that in the above case, the ".Finalize()" will be called when the event is "ServiceRequest1Registered" and context.Data.ServiceTypeId != (int)ServiceType.IndividualService. We dont want to continue if the ServiceTypeId != IndividualService. Only when event is "ServiceRequest1Registered" and ServiceTypeId = IndividualService, we want to send the "ServiceRequest2RegisteredCommand". Otherwise can you please explain why this will unconditionally finalize the state machine? – Rahul Jacob Oct 26 '20 at 04:08
  • You're right, I missed the additional condition (need to scroll to see it). – Alexey Zimarev Oct 26 '20 at 09:58

2 Answers2

0

If you are observing events delivered to the state machine prior to the Initially event completing, you should add UseInMemoryOutbox to the receive endpoint (before the state machine configuration). This will defer outbound messages until the state machine instance is persisted. My suspicion is that you are using an optimistic locking strategy, and the second event is arriving prior to the initial event persistence completing.

Chris Patterson
  • 28,659
  • 3
  • 47
  • 59
  • I tried adding "UseInMemoryOutbox" to the receive endpoint (before the state machine configuration) but with same result. The code for the masstransit setup has been added to the question above. Probably a wrong question but can we handle the second event in the initially block itself, as that seems to be working fine now. – Rahul Jacob Oct 26 '20 at 18:40
  • Okay. I don't entirely understand what you're trying to do, or why you even have a saga in this case, but if it's working for you that's great. – Chris Patterson Oct 26 '20 at 21:16
  • You are right, it does not serve the purpose of using saga when handling all in initially block. Anyways, good news is I found the solution. I was able to identify from logs that the aggregateid used as correlationid was different when handling the second event. This resulted in the second event arriving in initially block. Now we are having a different problem while using efcore for saga persistence. This is described in the post here. https://stackoverflow.com/questions/64555037/multiple-state-machines-with-automatonymous-using-efcore-persistence-not-working – Rahul Jacob Oct 27 '20 at 13:07
0

Correlation Id was different for 2nd event. This resulted in the 2nd event hitting initially. Corrected that issue and rest all worked. Ensure all events in the statemachine has the same correlationid and that this correlationid does not change during the orchestration.

Rahul Jacob
  • 19
  • 1
  • 5