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