Search code examples
c#masstransitsagaautomatonymous

Dependency Injection problem with Masstransit Sagas and Custom Activities


I am implementing a Saga for a distributed system. So far I haven't got any problem using Masstransit documentation to implement a Saga without any meaningful behavior (just an orchestrator with a couple of microservices exchanging messages).

The next step was adding some behavior between state changes of the Automatonymous state machine. I have a service with all the required behavior so I want to create customs activities that use this service using dependency injection.

The problem is that the activities do not resolve well and the following error is thrown:

Automatonymous.EventExecutionException: The ArchivosEnviados<ArchivosEnviadosAAlfresco> (Event) execution faulted
 ---> System.MissingMethodException: No parameterless constructor defined for type 'Confuturo.Microservicios.BackendAppSucursalVirtual.Sagas.Activities.EnviarCorreoActivity'.
   at System.RuntimeType.CreateInstanceDefaultCtorSlow(Boolean publicOnly, Boolean wrapExceptions, Boolean fillCache)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, Boolean wrapExceptions)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic, Boolean wrapExceptions)
   at System.Activator.CreateInstance(Type type)
   at Automatonymous.DefaultConstructorStateMachineActivityFactory.GetActivity[TActivity,TInstance](BehaviorContext`1 context)
   at Automatonymous.Activities.ContainerFactoryActivity`2.Automatonymous.Activity<TInstance>.Execute(BehaviorContext`1 context, Behavior`1 next)
   at Automatonymous.Activities.SlimActivity`2.Automatonymous.Activity<TInstance,TData>.Execute(BehaviorContext`2 context, Behavior`2 behavior)
   at Automatonymous.Activities.DataConverterActivity`2.Automatonymous.Activity<TInstance>.Execute[T](BehaviorContext`2 context, Behavior`2 next)
   at Automatonymous.Behaviors.ActivityBehavior`1.Automatonymous.Behavior<TInstance>.Execute[T](BehaviorContext`2 context)
   --- End of inner exception stack trace ---
   at Automatonymous.Behaviors.ExceptionBehavior`2.Automatonymous.Behavior<TInstance,TData>.Faulted[TException](BehaviorExceptionContext`3 context)
   at Automatonymous.Activities.SlimActivity`2.Automatonymous.Activity<TInstance,TData>.Faulted[TException](BehaviorExceptionContext`3 context, Behavior`2 next)
   at Automatonymous.Activities.DataConverterActivity`2.Automatonymous.Activity<TInstance>.Faulted[T,TException](BehaviorExceptionContext`3 context, Behavior`2 next)
   at Automatonymous.Behaviors.LastBehavior`1.Automatonymous.Behavior<TInstance>.Faulted[T,TException](BehaviorExceptionContext`3 context)
   at Automatonymous.Behaviors.DataBehavior`2.Automatonymous.Behavior<TInstance,TData>.Faulted[TException](BehaviorExceptionContext`3 context)
   at Automatonymous.Behaviors.ExceptionTypeCache.CachedConfigurator`1.Automatonymous.Behaviors.ExceptionTypeCache.CachedConfigurator.Faulted[TInstance,TData](Behavior`2 behavior, BehaviorContext`2 context, Exception exception)
   at Automatonymous.Behaviors.ExceptionTypeCache.Faulted[TInstance,TData](Behavior`2 behavior, BehaviorContext`2 context, Exception exception)
   at Automatonymous.Behaviors.ActivityBehavior`1.Automatonymous.Behavior<TInstance>.Execute[T](BehaviorContext`2 context)
   at Automatonymous.States.StateMachineState`1.Automatonymous.State<TInstance>.Raise[T](EventContext`2 context)
   at Automatonymous.States.StateMachineState`1.Automatonymous.State<TInstance>.Raise[T](EventContext`2 context)
   at Automatonymous.AutomatonymousStateMachine`1.Automatonymous.StateMachine<TInstance>.RaiseEvent[T](EventContext`2 context)
   at Automatonymous.Pipeline.StateMachineSagaMessageFilter`2.Send(SagaConsumeContext`2 context, IPipe`1 next)
   at Automatonymous.Pipeline.StateMachineSagaMessageFilter`2.Send(SagaConsumeContext`2 context, IPipe`1 next)
   at MassTransit.Saga.SendSagaPipe`2.Send(SagaRepositoryContext`2 context)
   at MassTransit.Saga.SendSagaPipe`2.Send(SagaRepositoryContext`2 context)
   at MassTransit.Saga.InMemoryRepository.InMemorySagaRepositoryContextFactory`1.Send[T](ConsumeContext`1 context, IPipe`1 next)
   at MassTransit.Saga.Pipeline.Filters.CorrelatedSagaFilter`2.GreenPipes.IFilter<MassTransit.ConsumeContext<TMessage>>.Send(ConsumeContext`1 context, IPipe`1 next) 

The documentation does not explicitly states how we should include custom activities using the default DI system of .NET Core. I also looked up for this information in Stackoverflow, the Github repo and Google Group.

In my Startup class I configure Masstransit as is stated in the official docs:

services.AddMassTransit(c =>
            {
                var machineGenerica = new SolicitudGenericStateMachine();
                var repositoryGenerico = new InMemorySagaRepository<SolicitudGenericInstance>();                      

                c.AddConsumer<MensajeDocumentoFirmadoConsumer>();
                c.AddBus(context => Bus.Factory.CreateUsingActiveMq(cfg =>
                {
                    cfg.ReceiveEndpoint("solicitud-generica-saga", e =>
                    {
                        e.StateMachineSaga(machineGenerica, repositoryGenerico);
                    });                       
                    cfg.UseHealthCheck(context);
                    cfg.Host(activeMqOptions.Host, activeMqOptions.Port, hostConfigure =>
                    {
                        hostConfigure.Username(activeMqOptions.Username);
                        hostConfigure.Password(activeMqOptions.Password);
                    });

                    cfg.ReceiveEndpoint(activeMqOptions.EndpointDocumentoFirmado, ep =>
                    {
                        ep.PrefetchCount = 1;
                        ep.Consumer<MensajeDocumentoFirmadoConsumer>(context);
                    });
                }));                
            });
services.AddMassTransitHostedService();               
services.AddScoped<EnviarCorreoActivity>();

If I understand correctly I have to use somehow the IStateMachineActivityFactory but I am really lost on how to proceed. Any help would be greatly appreciated.


Solution

  • You should register your state machine in the container using:

    c.AddSagaStateMachine< SolicitudGenericStateMachine, SolicitudGenericInstance >()
        .InMemoryRepository();
    

    Then, configure the saga using:

    cfg.ReceiveEndpoint("solicitud-generica-saga", e =>
    {
        e.ConfigureSaga<SolicitudGenericInstance>(context);
    });  
    

    The activity you have registered as scoped, is correct.