0

Newbie question - what am I missing? Are there any dotnetcore 2.2 Saga examples available?

I have a basic end to end system working OK with messages flowing across containers in docker-compose, but adding a Saga seems to be a challenge -

Q. Am I missing a scheduler dependency? In MassTransit 5.5.5, cfg.UseInMemoryMessageScheduler(); doesn't compile.

Something odd was going on, I had to mark my state machine explicitly as ISaga

MassTransit.ConfigurationException: Failed to create the state machine connector for Model.WorkflowExecutionStateMachine ---> MassTransit.ConfigurationException: The state machine was not properly configured: workflowapi_1 | [Failure] ExecutingTask was not specified


    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        // Register MassTransit
        services.AddMassTransit(x =>
        {
            x.AddConsumer<WorkflowTaskConsumer>();

            // required?
            x.AddSaga<WorkflowExecutionSaga>();

            x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
            {
                var rabbitMQHostName = $"rabbitmq://{configuration["RabbitMQHostName"]}";

                Console.Out.WriteLineAsync($"Starting Workflow Receiver... {rabbitMQHostName}/{QueueNames.ExeuteWorkflowTaskQueue}");

                var host = cfg.Host(new Uri(rabbitMQHostName), hostConfig =>
                {
                    hostConfig.Username("guest");
                    hostConfig.Password("guest");
                });

                // A basic message works OK
                cfg.ReceiveEndpoint(host, QueueNames.ExeuteWorkflowTaskQueue, ep =>
                {
                    ep.PrefetchCount = 1;
                    ep.UseMessageRetry(mr => mr.Interval(1000, 2));
                    ep.ConfigureConsumer<WorkflowTaskConsumer>(provider);
                });

                // Doesn't like this
                cfg.ReceiveEndpoint(host, QueueNames.WorkflowStateMachineSagaQueueName, ep =>
                {
                    ep.PrefetchCount = 1;
                    ep.UseMessageRetry(mr => mr.Interval(1000, 2));
                    ep.StateMachineSaga(new WorkflowExecutionSaga(), new InMemorySagaRepository<WorkflowExecutionStateMachine>());
                });
            }));

            cfg.UseInMemoryMessageScheduler(); // doesn't compile!
        });
    }

Bus is started as follows -


    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            // The default HSTS value is 30 days. You may want to change this for production scenarios, 
            // see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseMvc();

        var bus = app.ApplicationServices.GetService<IBusControl>();
        var busHandle = TaskUtil.Await(() =>
        {
            return bus.StartAsync();
        });

        lifetime.ApplicationStopping.Register(() =>
        {
            busHandle.Stop();
        });
    }

Exception details are

Unhandled Exception: MassTransit.ConfigurationException: Failed to create the state machine connector for Rapid.Workflow.Api.Model.WorkflowExecutionStateMachine ---> MassTransit.ConfigurationException: The state machine was not properly configured: workflowapi_1 | [Failure] ExecutingTask was not specified workflowapi_1 | at Automatonymous.StateMachineConfigurationResult.CompileResults(IEnumerable1 results) workflowapi_1 | at Automatonymous.StateMachineConnectors.StateMachineConnector1.StateMachineEvents()+MoveNext() workflowapi_1 | at System.Collections.Generic.List1.AddEnumerable(IEnumerable1 enumerable) workflowapi_1 | at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source) workflowapi_1 | at Automatonymous.StateMachineConnectors.StateMachineConnector1..ctor(SagaStateMachine1 stateMachine) workflowapi_1 | --- End of inner exception stack trace --- workflowapi_1 | at Automatonymous.StateMachineConnectors.StateMachineConnector1..ctor(SagaStateMachine1 stateMachine) workflowapi_1 | at Automatonymous.SagaConfigurators.StateMachineSagaConfigurator1..ctor(SagaStateMachine1 stateMachine, ISagaRepository1 repository, ISagaConfigurationObserver observer) workflowapi_1 | at MassTransit.AutomatonymousReceiveEndpointExtensions.StateMachineSaga[TInstance](IReceiveEndpointConfigurator configurator, SagaStateMachine1 stateMachine, ISagaRepository1 repository, Action`1 configure) workflowapi_1 | at Rapid.Workflow.Api.Startup.<>c.b__2_5(IRabbitMqReceiveEndpointConfigurator ep) in /src/Workflow.Api/Startup.cs:line 74

Dependencies are

<PackageReference Include="Automatonymous" Version="4.1.6" />
<PackageReference Include="MassTransit" Version="5.5.5" />
<PackageReference Include="MassTransit.RabbitMQ" Version="5.5.5" />
<PackageReference Include="MassTransit.AspNetCore" Version="5.5.5" />
<PackageReference Include="MassTransit.Automatonymous" Version="5.5.5" /> 
<PackageReference Include="MassTransit.Extensions.DependencyInjection" Version="5.5.5" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />

Thanks for any tips or ideas -

kenno
  • 333
  • 2
  • 7
  • I've found an example ... https://github.com/selcukusta/masstransit-saga-implementation – kenno Sep 21 '19 at 10:42

2 Answers2

1

You need to change use the .AddStateMachineSaga method, instead of the .AddSaga method you're using in the code in question.

// required? - yes, but should be as shown here
x.AddSagaStateMachine<WorkflowExecutionSaga, WorkflowState>();

In this case, both the state machine and the state machine instance type are required. Then, in your endpoint, use:

ep.ConfigureSaga<WorkflowState>(provider);

You also need to make sure you have the saga repository configured in the container, which is done for MS DI/in-memory using:

x.AddSingleton<ISagaRepository<WorkflowState>, InMemorySagaRepository<WorkflowState>>();

That should get you rolling, assuming your state machine isn't broken. If you still get the error, make sure all your state machine events, etc. are properly configured.

Also, your state machine instance should implement:

public class WorkflowState :
    SagaStateMachineInstance

And your state machine does not need to implement ISaga.

public class WorkflowExecutionSaga :
    MassTransitStateMachine<WorkflowState>
Chris Patterson
  • 28,659
  • 3
  • 47
  • 59
  • Thanks - will check, and remove ISaga (I realised that MassTransitSaga inherits this... VS Code...!?) Apologies for not posting my Saga code - it had unused events declared - does that error make sense? I think something is correlating the events with handlers in the Saga, but of course that event isn't there yet... – kenno Sep 21 '19 at 13:39
  • Something odd going on ... removing ISaga returns a build error (running dotnet build in VS code at sln level) Startup.cs(39,19): error CS0311: The type '... WorkflowExecutionSaga' cannot be used as type parameter 'T' in the generic type or method 'IRegistrationConfigurator.AddSaga(Action>)'. There is no implicit reference conversion from '... WorkflowExecutionSaga' to 'MassTransit.Saga.ISaga'. Attaching it directly seems to be OK (it may be a pre-release SDK issue?) – kenno Sep 21 '19 at 14:12
  • 1
    I don't think so, you're using the wrong method most likely. Check out the unit tests for the container registration code, and make sure you have all the right extension assemblies referenced: https://github.com/MassTransit/MassTransit/blob/develop/src/Containers/MassTransit.Containers.Tests/DependencyInjection_Tests/DependencyInjection_SagaStateMachine.cs – Chris Patterson Sep 21 '19 at 14:24
0

This error seems to crop up because the Saga class had declared some as-yet-unused (but public) events - DOH!

The solution was to remove unused events from the Saga...

// uncomment will fail! public Event<ISatelliteTaskRequest> UnusedEvent { get; private set; }

After looking at this sample https://github.com/selcukusta/masstransit-saga-implementation and cutting my program.cs back to basics - I was still getting the error! So, not a container / IOC / Startup issue.

Next, looking in source for the MassTransit error message (https://github.com/MassTransit/MassTransit/blob/master/src/MassTransit.AutomatonymousIntegration/Configuration/StateMachineConnectors/StateMachineConnector.cs) I realised that the correlating code is possibly reflecting on all public members of the Saga -

So, removing unused events from the Saga class fixes the issue.

kenno
  • 333
  • 2
  • 7
  • 2
    Yes, events are discovered on the type - and if you haven't configured them with proper correlation, they'll cause an error. Would help if the error message was more descriptive though! – Chris Patterson Sep 21 '19 at 13:54