Skip to content

Sagas Error Handling

The Suite Framework provides features to handle exceptions that occur in the execution of a saga that coordinates a routing slip.

To be able to implement this error handling we should follow some steps.

Activity

Surround your activity (or activities, if apply) logic with a try catch, in the catch block you must catch any exception that could occur during the execution of the activity and use the FaultedWithExceptions extension method in the current context. The activity should look like this:

Text Only
public async Task<ExecutionResult> Execute(ExecuteContext<Arguments> context)
{
    try
    {
        // Some code.
        return context.Completed();
    }
    catch (Exception ex)
    {
        return context.FaultedWithExceptions(ex);
    }
}

Subscription

Add a subscription in your routing slip to handle the RoutingSlipFaulted event and set the RoutingSlipEventContents to Variables and Itinerary, with this configuration you are going to be able to gather all the exceptions that may occur during the execution of the routing slip.

In the previous subscription you have to use the send endpoint that is available to fire a message that implements the IHasError interface, the implementation of this contract will force you to implement two properties in the failure message, Variables and ActivityExceptions Once you already have the previous steps covered now you must add a step in your saga to manage the mentioned message. This step could look like this:

Text Only
1
2
3
4
    DuringAny(
        When(FailureMessage)
        .SendErrorToRequestor()
        .Finalize());

Saga event

You have to make sure that you are listening to this message in any step of the saga (hence the DuringAny block) and implement the SendToRequestor extension method, this method have some basic logic that should be executed but you could safely add some extra behavior if needed. The basic behavior should be something like this:

Text Only
internal static EventActivityBinder<TSagaState, TFailureMessage> SendErrorToRequestor(
        this EventActivityBinder<TSagaState, TFailureMessage> binder)
{
    return binder.If(
        ctx => ctx.Saga.ResponseAddress is not null,
        binder => binder.Then(async ctx =>
        {
            var sendEndpoint = await ctx.GetSendEndpoint(
                ctx.Saga.ResponseAddress!);
            var exceptions = ctx.Message.ActivityExceptions.Select(
                x => x.ExceptionInfo);

            exceptions = ctx.Message.Variables
                        .RetrieveExceptionsFromVariables() is not null
                ? exceptions.Concat(ctx.Message.Variables
                                    .RetrieveExceptionsFromVariables()!)
                : exceptions;

                await sendEndpoint.Send<ErrorMessage>(
                    new
                    {
                        ctx.Saga.CorrelationId,
                        ExceptionsInfo = exceptions.ToArray(),
                    },
                    sendContext => sendContext.RequestId = ctx.Saga.RequestId);
        }));
}

Where TSagaState is the saga state and TFailureMessage is your message that implements the IHasError contract.