NServiceBus Removing IBus - Utilising IPipelineContext and IMessageSession

669 Views Asked by At

I am in the process of migrating NServiceBus up to v6 and am at a roadblock in the process of removing reference to IBus.

We build upon a common library for many of our applications (Website, Micro Services etc) and this library has the concept of IEventPublisher which is essentially a Send and Publish interface. This library has no knowledge of NSB. We can then supply the implementation of this IEventPublisher using DI from the application, this allows the library's message passing to be replaced with another technology very easily.

So what we end up with is an implementation similar to

public class NsbEventPublisher : IEventPublisher
{
    IEndpointInstance _instance;

    public NsbEventPublisher(IEndpointInstance endpoint)
    {
        instance = endpoint;
    }

    public void Send(object message)
    {
        instance.Send(message, sendOptions);
    }

    public void Publish(object message)
    {
        instance.Publish(message, sendOptions);
    }
}

This is a simplification of what actually happens but illustrates my problem. Now when the DI container is asked for an IEventPublisher it knows to return a NsbEventPublisher and it knows to resolve the IEndpointInstance as we bind this in the bootstrapper for the website to the container as a singleton. All is fine and my site runs perfect.

I am now migrating the micro-services (running in NSB.Host) and the DI container is refusing to resolve IEndpointInstance when resolving the dependencies within a message handler. Reading the docs this is intentional and I should be using IMessageHandlerContext when in a message handler. https://docs.particular.net/nservicebus/upgrades/5to6/moving-away-from-ibus The docs even elude to the issue I have in the bottom example around the class MyContextAccessingDependency. The suggestion is to pass the message context through the method which puts a hard dependency on the code running in the context of a message handler.

What I would like to do is have access to a sender/publisher and the DI container can give me the correct implementation. The code does not need any concept of the caller and if it was called from a message handler or from a self hosted application that just wants to publish.

I see that there is two interfaces for communicating with the "Bus" IPipelineContext and IMessageSession which IMessageHandlerContext and IEndpointInstance interfaces extend respectively. What I am wondering is there some unification of the two interfaces that gets bound by NSB into the container so I can accept an interface that sends/publishes messages. In a handler it is an IMessageHandlerContext and on my self hosted application the IEndPointInstance.

For now I am looking to change my implementation of IEventPublisher depending on application hosting. I was just hoping there might be some discussion about how this approach is modeled without a reliable interface to send/publish irrespective of what initiated the execution of the code path.

1

There are 1 best solutions below

3
On

A few things to note before I get to the code:

  • The abstraction over abstraction promise, never works. I have never seen the argument of "I'm going to abstract ESB/Messaging/Database/ORM so that I can swap it in future" work. ever.

  • When you abstract message sending functionality like that, you'll lose some of the features the library provides. In this case, you can't perform 'Conversations' or use 'Sagas' which would hinder your overall experience, e.g. when using monitoring tools and watching diagrams in ServiceInsight, you won't see the whole picture but only nugets of messages passing through the system.

Now in order to make that work, you need to register IEndpointInstance in your container when your endpoint starts up. Then that interface can be used in your dependency injection e.g. in NsbEventPublisher to send the messages.

Something like this (depending which IoC container you're using, here I assume Autofac):

static async Task AsyncMain()
{
    IEndpointInstance endpoint = null;

    var builder = new ContainerBuilder();

    builder.Register(x => endpoint)
           .As<IEndpointInstance>()
           .SingleInstance();   

    //Endpoint configuration goes here...

    endpoint = await Endpoint.Start(busConfiguration)
                             .ConfigureAwait(false);
}

The issues with using IEndpointInstance / IMessageSession are mentioned here.