Unity Interception with MessageHandlers?


#1

I have some boiler plate code I would like to apply to every message handler.
While it happens in the catch handler, it is basically just logging.

        catch (Exception) when (ShutdownManagementService.CancellationToken.IsCancellationRequested)
        {
            throw;
        }
        catch (Exception e) when (e.IsSqlDeadlockException())
        {
            Log.Warn(FormattableString.Invariant($"Database deadlock occurred, retrying..."));
            Metrics.DeadlockExceptionMeter.Mark();
            throw;
        }
        catch (Exception e)
        {
            Log.Warn(e);
            throw;
        }

I can hook up my own LoggingInterceptorBehavior::IInterceptionBehavior class via Unity, but I only know how to do this when I register the Type with Unity. NSB is responsible for registering the IHandlesMessages implementations with Unity, so I have not been able to figure out how to add the logging behavior to the endpoint handlers.

Has anyone else tried to use Unity AoP with NSB?


(Dennis van der Stelt) #2

Hi,

First of all, I’m not really sure what you’re trying to achieve? It seems you want to properly deal with SQL deadlocks?

The idea is that if NServiceBus detects that something is off, it logs this error and immediately retries the message. Because a deadlock is usually immediately solved. So it tries several times. We call this immediate retries.

If this all fails, it could be that a webservice or database is temporarily unavailable. We then wait a short period of time and retry messages again. This is what we call delayed retries.

Read more about recoverability in NServiceBus here : https://docs.particular.net/nservicebus/recoverability/

If you want to know how often these retries are happening, we’ve released the first Release Candidate for our new monitoring features. Read more about them here : Real-time Platform Monitoring RC1 available

Again, please let us know what it is you’re actually trying to achieve. There might be better solutions, like the retries we are using.


#3

No. I want to put logging around the exceptions to log them as warnings, and let NSB go through first and second level retries. I add useful meta-data to the logging of these exceptions based on the parameters passed in so that issues can be diagnosed. Getting a generic “DivisionByZero” exception from NSB doesn’t help, because there is no context about what was being done… And yes, I am using a thread logical context for an operation identifier.

You ask, “What am I trying to achieve?”.
Answer, Apply aspect oriented programming techniques against NSB message handlers using Unity. In my example, I was referring to logging and exception handling, but, it could be anything, from timing methods, to incrementing metric counters, etc


(Daniel Marbach) #4

Hi,
NServiceBus should resolve the concrete handler type if I remember correctly. Can’t you add an intercepter registration for that concrete type? Then when the type is resolved from NSB over unity it should also load your interceptor

Daniel


#5

I thought so to and tried that initially, but I get an exception when starting the endpoint that the type has already been registered. I’ll play around with it a little more, maybe I did something else wrong and didn’t realize it.


#6

If I register the handler and add an interface interceptor to it, it works if I resolve directly from the UnityContainer.

However, after I have passed this container to NSB (use existing container), when the endpoint starts, it immediately throws an ArgumentException: “The type is not interceptable.”


(Mike Minutillo) #7

I think NServiceBus will still register the handler in the container even if you supply an existing container. This registration is going to override the previous one that includes the interception configuration.

Another way to achieve interception on all handlers is to add a custom behavior to the NServiceBus pipeline.

public class LogExceptionsBehavior : Behavior<IInvokeHandlerContext>
{
  public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
  {
    try
    {
      await next().ConfigureAwait(false);
    }
    catch (Exception) when (ShutdownManagementService.CancellationToken.IsCancellationRequested)
    {
      throw;
    }
    catch (Exception e) when (e.IsSqlDeadlockException())
    {
      Log.Warn(FormattableString.Invariant($"Database deadlock occurred, retrying..."));
      Metrics.DeadlockExceptionMeter.Mark();
      throw;
    }
    catch (Exception e)
    {
      Log.Warn(e);
      throw;
    }
  }
}

To register it into the pipeline you need some code in your endpoint configuration:

endpointConfiguration.Pipeline.Register(
  behavior: new LogExceptionsBehavior(),
  description: "Logs exceptions");

You can find out more about the pipeline and how to extend it here.