Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
540 views
in Technique[技术] by (71.8m points)

c# - 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.

question from:https://stackoverflow.com/questions/65903168/how-to-setup-a-masstransit-order-state-machine

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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). E.g.,

            //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. E.g.,

            //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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...