简体   繁体   中英

How to setup a Masstransit Order state machine?

I have this simple process for my order process spanning multiple microservices. Created > validated> Checkout session created > payed > collected. The result can fail at any step resulting in the "Cancelled" state. I currently have the following as my state machine:

    {
        public Event<IOrderCreatedEvent> StartOrderProcess { get; private set; }
        public Event<IOrderValidatedEvent> OrderValidated { get; private set; }
        public Event<IOrderCheckoutSessionCreatedEvent> CheckoutSessionCreatedEvent { get; private set; }
        public Event<IOrderPayedEvent> OrderPayed { get; private set; }
        public Event<IOrderCollectedEvent> OrderCollected { get; private set; }
        public Event<IOrderCancelledEvent> OrderCancelled { get; private set; }

        public State Validated { get; private set; }
        public State CheckoutSessionCreated { get; private set; }
        public State Payed { get; private set; }
        public State Collected { get; private set; }

        public State Cancelled { get; private set; }

        
        //Start => Validated => Session created => Payed => Collected 
        public OrderStateMachine()
        {
            InstanceState(s => s.CurrentState);
            Event(() => StartOrderProcess, x => x.CorrelateById(m => m.Message.OrderId));
            Event(() => OrderValidated, x => x.CorrelateById(m => m.Message.OrderId));
            Event(() => CheckoutSessionCreatedEvent, x => x.CorrelateById(m => m.Message.OrderId));
            Event(() => OrderPayed, x => x.CorrelateById(m => m.Message.OrderId));
            Event(() => OrderCollected, x => x.CorrelateById(m => m.Message.OrderId));

            Event(() => OrderCancelled, x => x.CorrelateById(m => m.Message.OrderId));

            //Start
            Initially(
                When(StartOrderProcess)
                    .Then(context =>
                    {
                        context.Instance.OrderId = context.Data.OrderId;
                        context.Instance.PackagesId = context.Data.PackagesIds;
                    })
                    .Publish(context => new OrderValidatedEvent(context.Instance))
                    .TransitionTo(Validated)
            );

            //Validated => payment session created
            During(Validated, When(
                    CheckoutSessionCreatedEvent).Then(context =>
                {
                    context.Instance.OrderId = context.Data.OrderId;
                    context.Instance.PackagesId = context.Data.PackagesIds;
                    context.Instance.CheckoutSessionId = context.Data.SessionId;
                })
                .Publish(context => new OrderCheckoutSessionCreatedEvent(context.Instance))
                .TransitionTo(CheckoutSessionCreated)
            );
            
            //Session created => payed
            During(CheckoutSessionCreated, When(
                    OrderPayed).Then(context =>
                {
                    context.Instance.OrderId = context.Data.OrderId;
                    context.Instance.PackagesId = context.Data.PackagesIds;
                    context.Instance.PaymentMethod = context.Data.PaymentMethod;
                })
                .Publish(context => new OrderPayedEvent(context.Instance))
                .TransitionTo(Payed)
            );
            
            //payed => Collected
            During(Payed, When(
                    OrderCollected).Then(context =>
                {
                    context.Instance.OrderId = context.Data.OrderId;
                    context.Instance.PackagesId = context.Data.PackagesIds;
                    context.Instance.OrderCollectedDateTime = DateTime.Now;
                })
                .Publish(context => new OrderCollectedEvent(context.Instance))
                .TransitionTo(Collected)
            );
            //Cancel order bij cancelled event
            DuringAny(
                When(OrderCancelled)
                    .Then(context =>
                    {
                        context.Instance.OrderCancelDateTime = DateTime.Now;
                        context.Instance.OrderId = context.Data.OrderId;
                    })
                    .Finalize());


            SetCompletedWhenFinalized();
        }
    }

And this is the content of "OrderStateData"

    public class OrderStateData : SagaStateMachineInstance
    {
        public Guid CorrelationId { get; set; }
        public string CurrentState { get; set; }
        public DateTime? OrderCreationDateTime { get; set; }
        public DateTime? OrderCancelDateTime { get; set; }
        public DateTime? OrderCollectedDateTime { get; set; }

        public Guid OrderId { get; set; }
        public List<Guid> PackagesId { get; set; }
        public string CheckoutSessionId { get; set; }
        public string PaymentMethod { get; set; }
    }

However when I try to use this I get the following error

      R-FAULT rabbitmq://localhost/Constants.OrderBus1 5f750000-61e0-00d8-0485-08d8c20620a5 MessagingContracts.Orders.IOrderValidatedEvent FS_Saga.OrderStateMachine.OrderStateData(00:00:00.1109663)
      Automatonymous.NotAcceptedStateMachineException: FS_Saga.OrderStateMachine.OrderStateData(654b5d93-cb5a-4f58-a06c-3a25ac56532d) Saga exception on receipt of MessagingContracts.Orders.IOrderValidatedEvent: Not accepted in state Validated
       ---> Automatonymous.UnhandledEventException: The OrderValidated event is not handled during the Validated state for the OrderStateMachine state machine
         at Automatonymous.AutomatonymousStateMachine`1.DefaultUnhandledEventCallback(UnhandledEventContext`1 context)
         at Automatonymous.AutomatonymousStateMachine`1.UnhandledEvent(EventContext`1 context, State state)
         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)
         --- End of inner exception stack trace ---
         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)

I have a strong feeling my state machine is absolute garbage, but I cant find anything on how to set it up properly.

It looks like a OrderValidated event is occurring while the state is Validated - which is currently unhandled (only CheckoutSessionCreatedEvent is handled at the moment). It is important to note that even though publishing the OrderValidated event is technically occurring prior to the transition to Validated , the consumption of the event is occurring after the transition to Validated state (we're dealing with message brokers here).

Basically, your behavior logic is expecting a When(OrderValidated) within During(Validated) . Eg,

            //Validated => payment session created
            During(Validated, When(
                    CheckoutSessionCreatedEvent).Then(context =>
                {
                    context.Instance.OrderId = context.Data.OrderId;
                    context.Instance.PackagesId = context.Data.PackagesIds;
                    context.Instance.CheckoutSessionId = context.Data.SessionId;
                })
                .Publish(context => new OrderCheckoutSessionCreatedEvent(context.Instance))
                .TransitionTo(CheckoutSessionCreated),
                When(OrderValidated).Then({ /* Do some stuff */ });

Chris is right in his comment though, it looks like you should remove the OrderValidated event if your not going to be used for anything.

If you're half-way through something you could always just temporarily ignore the event using Ignore . Eg,

            //Validated => payment session created
            During(Validated, When(
                    CheckoutSessionCreatedEvent).Then(context =>
                {
                    context.Instance.OrderId = context.Data.OrderId;
                    context.Instance.PackagesId = context.Data.PackagesIds;
                    context.Instance.CheckoutSessionId = context.Data.SessionId;
                })
                .Publish(context => new OrderCheckoutSessionCreatedEvent(context.Instance))
                .TransitionTo(CheckoutSessionCreated),
                Ignore(OrderValidatedEvent));

Hopefully this makes sense. Correct me if I'm wrong @Chris Patterson.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM