I have a system where I try to implement a middleware for our APIs to have idempotency handled. for this all of the APIs send a message to system where another service takes these messages and either forwards them to real handler with minor data juggling, or responds by itself.
frontend system (client) shall send TRequestWithIdempotency message and expect a TResponseWithIdempotency message back. internal TRequestInternal and TResponseInternal shall be used if state machine is new and API operation has not been processed/executed yet (operation result not known).
I tried to use StateMachines since I saw an example at https://github.com/MassTransit/MassTransit/blob/develop/tests/MassTransit.QuartzIntegration.Tests/RequestRequest_Specs.cs which is very similar to what I am trying. also I changed the test code and system works there good but at below the system fails at end of operation flow. I can see logs about internal API operations successful and also I can see response in logs but at one point before replying to frontend (giving back the TResponseWithIdempotency reply), the mentioned error occurs.
my internal consumer and RequestStateMachine is registered to system as ConfigureInMemoryBus function in the test file from github.
let me know if something is missing. My state machine does not have a Completed state as in error and the error comes from RequestState which I do not change or touch.
I use MassTransit 7.3.1 (not 8 unfortunately), Localstack
// TRequest and TResponse are same with TRequestWithIdempotency and TResponseWithIdempotency types in statemachine
var requestClient = bus.CreateRequestClient<TRequest>(RequestTimeout.After(s: 120));
var requestHandle = requestClient.Create(request);
var response = requestHandle.GetResponse<TResponse>(false);
my operation state
public class IdempotentPurchaseOperationState<TResponse> : SagaStateMachineInstance
{
public Guid Id => CorrelationId;
public Guid CorrelationId { get; set; }
public int CurrentState { get; set; }
public Guid? MyRequestId { get; set; }
public TResponse? ResponseMessage { get; set; }
}
my state machine definition
// there are some Get... functions which convert the data and returns proper data instances
public State InternalRequestCompleted { get; private set; }
public State InternalRequestFaulted { get; private set; }
public Request<TState, TRequestInternal, TResponseInternal, IdempotentRequestFault> RequestFromHandler { get; private set; }
public Event<TRequestWithIdempotency> IdempotentRequestReceived { get; private set; }
public IdempotentPurchaseOperationProcessManager()
{
InstanceState(x => x.CurrentState, InternalRequestCompleted, InternalRequestFaulted);
Event(() => IdempotentRequestReceived, x =>
{
x.CorrelateById(x => x.CorrelationId ?? GetIdempotencyKey(x.Message));
x.SetSagaFactory(e => new TState
{
CorrelationId = GetIdempotencyKey(e.Message),
});
});
Request(() => RequestFromHandler, x => x.MyRequestId, x => x.Timeout = TimeSpan.FromSeconds(30));
Initially(
When(IdempotentRequestReceived)
.Request(RequestFromHandler, ctx =>
{
return ctx.Init<TRequestInternal>(GetInternalRequestFromProcessManagerRequest(ctx.Data));
})
.RequestStarted()
.TransitionTo(RequestFromHandler.Pending)
);
During(RequestFromHandler.Pending,
When(RequestFromHandler.Completed)
.RequestCompleted(ctx =>
{
return ctx.Init<TResponseWithIdempotency>(GetProcessManagerResponseFromInternalResponse(ctx.Data));
})
.TransitionTo(InternalRequestCompleted),
When(RequestFromHandler.Completed2)
.RequestCompleted(context =>
{
return context.Init<IdempotentRequestFault>
(new IdempotentRequestFaultImpl
{
ExceptionMessages = context.Data.ExceptionMessages
});
})
.TransitionTo(InternalRequestFaulted),
When(RequestFromHandler.Faulted)
.RequestCompleted(context =>
{
return context.Init<Fault<TRequestInternal>>
(new
{
Message = context.Instance.InitialMessage,
context.Data.FaultId,
context.Data.FaultedMessageId,
context.Data.Timestamp,
context.Data.Exceptions,
context.Data.Host,
context.Data.FaultMessageTypes
});
})
.TransitionTo(InternalRequestFaulted)
);
During(InternalRequestCompleted,
When(IdempotentRequestReceived)
.RespondAsync(ctx => {
return ctx.Init<TResponseWithIdempotency>(ctx.Instance.ResponseMessage);
})
);
During(InternalRequestFaulted,
When(IdempotentRequestReceived)
.Finalize());
}
I get below exception. on next API call with same idempotency key I get correct and expected response.
sev="ERROR" msg="Message handling threw error." corrid="d9d7b1c8-d59f-4f02-bac1-03c35a91fc8b" reqid="01000000-ac14-0242-5b09-08da1e0e6950" op="Consumer:RequestCompleted" messageType="RequestCompleted" duration=339 Exception=Automatonymous.NotAcceptedStateMachineException: Automatonymous.Requests.RequestState(00060000-ac14-0242-af78-08da1d5a3ecf) Saga exception on receipt of Automatonymous.Contracts.RequestCompleted: Not accepted in state Final
---> Automatonymous.UnhandledEventException: The Completed event is not handled during the Final state for the RequestStateMachine 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)