Is there a way to create a generic IFailed<T> to catch unimplemented second level retries in Rebus?

87 Views Asked by At

Is there a way to create and register a generic IFailed that would catch second level retries that have no handlers?

We use second-level retries enabled and have our handlers implementing IHandleMessages<SomeCommand> as well as IHandleMessages<IFailed<SomeCommand>>.

However, not all our handlers are implementing the IFailed<SomeCommand> interface and this is causing some exceptions to bubble up multiple times when SomeCommand fails.

Is there a way to register a generic IHandleMessages<IFailed<T>> that will handle all failed commands that have not been handled properly?

I'm thinking of at least logging T has failed and we will not attempt second level retries or something similar if the command has failed.

1

There are 1 best solutions below

0
mookid8000 On BEST ANSWER

Yes there is

To get to handle all failed messages, you can simply register a handler that implements IHandleMessages<IFailed<object>>.

If you want it to be a "fallback" (i.e. only be used when there's no explicit handler for a given failed messages type), then you can decorate Rebus' IHandlerActivator and filter the returned handlers.

Here's how:

Create an extension method with a handler activator decorator

static class FallbackMessageHandlerExtensions
{
    /// <summary>
    /// Marks handlers of type <typeparamref name="THandler"/> as a "fallback handler", which means that it will
    /// only be used in cases where there are no other handlers.
    /// </summary>
    public static void MarkAsFallbackHandler<THandler>(this OptionsConfigurer configurer) where THandler : IHandleMessages
    {
        if (configurer == null) throw new ArgumentNullException(nameof(configurer));

        configurer.Decorate<IHandlerActivator>(c => new HandlerInvokerRemover<THandler>(c.Get<IHandlerActivator>()));
    }

    class HandlerInvokerRemover<THandlerType> : IHandlerActivator
    {
        readonly IHandlerActivator _handlerActivator;

        public HandlerInvokerRemover(IHandlerActivator handlerActivator)
        {
            _handlerActivator = handlerActivator ?? throw new ArgumentNullException(nameof(handlerActivator));
        }

        public async Task<IEnumerable<IHandleMessages<TMessage>>> GetHandlers<TMessage>(TMessage message, ITransactionContext transactionContext)
        {
            var handlers = await _handlerActivator.GetHandlers(message, transactionContext);
            var handlersList = handlers.ToList();

            // if there's more than one handler, there's potential for having included the 
            // fallback handler without having the need for a fallback
            if (handlersList.Count > 1)
            {
                handlersList.RemoveAll(h => h is THandlerType);
            }

            return handlersList;
        }
    }
}

and then ensure that second level retries and your fallback mechanism are enabled

services.AddRebus(
    configure => configure
        .(...)
        .Options(o =>
        {
            o.RetryStrategy(secondLevelRetriesEnabled: true);
            o.MarkAsFallbackHandler<FallbackFailedMessageHandler>();
        })
);

You can see a full example here: https://github.com/rebus-org/Rebus.ServiceProvider/blob/master/Rebus.ServiceProvider.Tests/Examples/AddMessageHandlerFallback.cs