masstransitautomatonymous

Automatonymous - Payload not found when calling RaiseEvent with Send Activity


I've been spinning my wheels trying to get the MassTransitStateMachine working and I seem to not understand exactly how this is supposed to work.

The error I'm receiving (full code below) is PayloadNotFoundException when I attempt to call stateMachine.RaiseEvent(instance, stateMachine.DeployApplicationRequest, request) combined with the .Send() Activity in the state machine. I've been trying to trace this through but have gotten nowhere.

Here's the relevant code and explanation of intent:

I receive a request to deploy an application via a webAPI post, and convert the request to a DeployApplicationRequest.

public record DeployApplicationRequest
    {
        // State machine properties
        public Guid CorrelationId { get; init; }
        public ChatApplication ChatApplication { get; init; }
        public string ChannelId { get; init; }
        public string UserId { get; init; }


        // Command
        public DeployApplication DeployApplication { get; init; }
    }

    public enum ChatApplication
    {
        Slack,
        Teams
    }

This request encapsulates two things: The state that the API needs to maintain so it knows how to route the results back to the requester (state machine properties) and the Command to be sent to the service bus.

The command, DeployApplication, looks like this:

public record DeployApplication
    {
        public Guid CorrelationId { get; init;}
        public string InitiatedBy { get; init; }
        public DateTime CreatedDate { get; init; }
        public string Environment { get; init; }
        public string PullRequestId { get; init; }
    }

I have created a state instance to preserve the details of the request (such as whether it came in via Slack or Teams). I do not want to publish this information to the bus:

public class DeployApplicationState : SagaStateMachineInstance
    {
        public Guid CorrelationId { get; set; }
        public string CurrentState { get; set; }

        public ChatApplication ChatApplication { get; set; }
        public string ChannelId { get; set; }
        public string UserId { get; set; }
    }

And I have created a StateMachine to handle this:

public class DeployApplicationStateMachine : MassTransitStateMachine<DeployApplicationState>
    {
        private readonly ILogger<DeployApplicationStateMachine> logger;
        public DeployApplicationStateMachine(ILogger<DeployApplicationStateMachine> logger)
        {
            this.logger = logger;

            InstanceState(x => x.CurrentState);

            Event(() => DeployApplicationRequest, x => x.CorrelateById(context => context.Message.CorrelationId));

            Initially(
                When(DeployApplicationRequest)
                    .Then(x => {
                        x.Instance.CorrelationId = x.Data.CorrelationId;
                        x.Instance.ChannelId = x.Data.ChannelId;
                        x.Instance.ChatApplication = x.Data.ChatApplication;
                        x.Instance.UserId = x.Data.UserId;
                    })
                    .Send(context => context.Init<DeployApplication>(context.Data.DeployApplication))
                    .TransitionTo(Submitted));
        }

        public Event<DeployApplicationRequest> DeployApplicationRequest { get; private set; }

        public State Submitted { get; private set; }
    }

To trigger the initial event (since the request is not coming in via a Consumer but rather through a controller), I have injected the state machine into the MassTransit client, and I'm calling the RaiseEvent method:

public class MassTransitDeployClient : IDeployClient
    {
        private readonly DeployApplicationStateMachine stateMachine;
        public MassTransitDeployClient(DeployApplicationStateMachine stateMachine)
        {
            this.stateMachine = stateMachine;
        }

        public async Task Send(DeployApplicationRequest request)
        {
            var instance = new DeployApplicationState
            {
                CorrelationId = request.CorrelationId,
                ChannelId = request.ChannelId,
                ChatApplication = request.ChatApplication,
                UserId = request.UserId
            };

            await stateMachine.RaiseEvent(instance, stateMachine.DeployApplicationRequest, request);
            // This works for sending to the bus, but I lose the state information
            //await sendEndpointProvider.Send(request.DeployApplication);
        }
    }

And the container configuration is as follows:

services.AddMassTransit(x =>
            {
                x.AddSagaStateMachine<DeployApplicationStateMachine, DeployApplicationState>()
                    .InMemoryRepository();

                x.UsingInMemory((context, cfg) =>
                {
                    cfg.ConfigureEndpoints(context);
                });
                
                
            });

            EndpointConvention.Map<DeployApplication>(new Uri($"queue:{typeof(DeployApplication).FullName}"));

            services.AddMassTransitHostedService();

Raising the event works just fine, and the state machine transfers to Submitted successfully if I do not include .Send(...) in the state machine. The second I introduce that Activity I get the PayloadNotFoundException. I've tried about 15 different ways of doing this over the weekend with no success, and I'm hoping someone may be able to help me see the error of my ways.

Thanks for reading! Fantastic library, by the way. I'll be looking for ways to contribute to this going forward, this is one of the most useful libraries I've come across (and Chris your Youtube videos are excellent).


Solution

  • Saga state machines are not meant to be invoked directly from controllers. You should send (or publish) a message that will then be dispatched to the saga via the message broker.

    IF you want to do it that way, you can use MassTransit Mediator instead, but you'll still be sending a message via Mediator to the saga, which will then be handled by MassTransit.

    TL;DR - you don't use RaiseEvent with saga state machines.