Skip to main content

NService... umm, where's the bus?

This post is part of a series describing the improvements in NServiceBus 6.0.

After many years of service, we bid our trusty IBus farewell. It’s served us well almost since the very beginning, providing access to many operations like sending messages, subscribing to events, and even manipulating headers.

Over the years, the IBus interface has become like a kitchen knife block, with knives for paring, chopping, cutting steak, and maybe even a pair of herb scissors. Although it’s convenient for storing many knives and makes them easily accessible, you don’t need all of them when you’re having a steak dinner. So it is with IBus. Methods like Reply and ForwardCurrentMessageTo are always available on the interface but only make sense in the context of handling an incoming message. If you use them in other scenarios, such as during endpoint startup, they may throw an exception.

With version 6 of NServiceBus, we’ve removed IBus entirely and replaced it with focused, context-specific interfaces that provide clear guidance on what can and can’t be done based on the methods they expose. This ensures that, in any given circumstance, you’ll have the exact tools needed for the job and nothing else cluttering up your workspace.

🔗Handlers

When implementing IHandleMessages<T>, you’ll notice an additional parameter on the Handle method. This parameter is of type IMessageHandlerContext, which exposes methods such as Publish and Send. More importantly, the IMessageHandlerContext type also excludes methods like Subscribe and Unsubscribe — actions that you really shouldn’t be performing in a message handler anyway. Migrating your handlers should only require replacing IBus with the newly provided IMessageHandlerContext. For example:

public class v5_MyMessageHandler : IHandleMessages<MyMessage>
{
    private IBus bus;

    public MyMessageHandler(IBus bus)
    {
        this.bus = bus;
    }

    public void Handle(MyMessage message)
    {
        var messageId = bus.CurrentMessageContext.Id;

        bus.Publish<MyEvent>(e => e.Value = messageId);
    }
}

becomes

public class v6_MyMessageHandler : IHandleMessages<MyMessage>
{
    public async Task Handle(MyMessage message, IMessageHandlerContext context)
    {
        await context.Publish<MyEvent>(e => e.Value = context.MessageId);
    }
}

As you can see, in addition to the removal of IBus in version 6 of NServiceBus, the API for handling messages is now async as well. More information on this change can be found in our documentation on asynchronous handlers.

🔗Moving off IBus

As you can imagine, removing IBus introduces many changes. For example, you no longer have to inject IBus into your classes using dependency injection. Instead, you’re provided with a context-specific object in the method you’re working with. Let’s take a look at how much simpler our endpoint startup and shutdown code can be without IBus:

public class v5_Startup : IWantToRunWhenBusStartsAndStops
{
    private readonly IBus bus;

    public Startup(IBus bus)
    {
        this.bus = bus;
    }

    public void Start()
    {
        bus.Publish<MyEvent>();
    }

    public void Stop()
    {
    }
}

becomes:

public class v6_Startup : IWantToRunWhenEndpointStartsAndStops
{
    public async Task Start(IMessageSession session)
    {
        await session.Publish<MyEvent>();
    }

    public async Task Stop(IMessageSession session)
    {
    }
}

The IMessageSession parameter presents you with just the operations that are valid at this specific extensiblity point.

🔗Endpoint configuration

The NServiceBus configuration API remains very similar, but it underwent some renaming. Instead of an IBus, you receive an IEndpointInstance, which offers all available bus operations outside the message processing pipeline. For example, here is how you would initialize an endpoint prior to sending a message or publishing an event:

BusConfiguration v5_busConfiguration = new BusConfiguration();
// other endpoint configuration code goes here
IStartableBus v5_startableBus = Bus.Create(v5_busConfiguration);
IBus v5_bus = v5_startableBus.Start();

//use v5_bus to .Send and/or .Publish as required

becomes

EndpointConfiguration v6_endpointConfiguration = new EndpointConfiguration();
// other endpoint configuration code goes here
IEndpointInstance v6_endpoint = await Endpoint.Start(v6_endpointConfiguration);

//use v6_endpoint to .Send and/or .Publish as required

Note that with the removal of IBus, Bus.Create has been replaced with the Endpoint.Start method, which allows you to start your endpoint with less ceremony.

🔗Impact on Dependency Injection

In previous versions of NServiceBus, the IBus would be registered in the IoC container automatically. With IBus no longer in use, this registration doesn’t need to happen. Additionally, the new context-specific interfaces will not be registered in the IoC container, either. Instead, as we saw in the samples above, they will be provided to the NServiceBus extensibility points as parameters.

That said, if you do need access to an IEndpointInstance from an IoC container—for example, if you need to send a message from an ASP.NET MVC Controller or a WPF ViewModel—it can be registered as shown here using the Ninject container:

IEndpointInstance endpoint = await Endpoint.Start(config);
kernel.Bind<IEndpointInstance>().ToConstant(endpoint);

Once the object instance is registered with the container, IEndpointInstance will be available via dependency injection wherever you may need it:

public class DefaultController :
   Controller
{
    IEndpointInstance endpoint;

    public DefaultController(IEndpointInstance endpoint)
    {
        this.endpoint = endpoint;
    }

    public ActionResult Index()
    {
        return View();
    }

    [AllowAnonymous]
    public async Task<ActionResult> Send()
    {
        await endpoint.Send("Samples.Mvc.Endpoint", new MyMessage())
            .ConfigureAwait(false);
        return RedirectToAction("Index", "Default");
    }
}

🔗Testing within a context

Context-specific interfaces also have a nice effect on the unit testing experience with NServiceBus, enabling the Arrange Act Assert pattern many developers know and love. It changes this…

Test.Handler<MyHandler>()
    .ExpectSend<OutgoingMessage>(m => true)
    .OnMessage(new IncomingMessage());

…to this:

var handler = new MyHandler();
var testableMessageHandlerContext = new TestableMessageHandlerContext();

await handler.Handle(new IncomingMessage(), testableMessageHandlerContext);

testableMessageHandlerContext.SentMessages.Should().HaveCount(1);

Although this may look like more effort to achieve the same result, this approach provides a lot more testing flexibility. The example above uses the FluentAssertions package to verify results instead of relying on the predefined assertions in the NServiceBus.Testing package. Also, if you don’t like the provided testing classes, you’re now able to create alternatives using your favorite mocking library.

🔗Summary

We’re really excited about the evolution from the IBus interface to more contextual ones. Besides making your code more testable, it simplifies your configuration and makes using the API a much richer experience.

So come and see how much easier it is to develop with the new context-specific interfaces in NServiceBus version 6.

Actual knives not included ;-)

🔗Footnotes

1 Unit Testing NServiceBus 6

About the Author: Tim Bussmann is a developer at Particular Software who loves building intention-revealing APIs. When not at his computer, he enjoys hiking in the mountains in his native Switzerland.

Share on Twitter
Don't miss a thing. Sign up today and we'll send you an email when new posts come out.
Thank you for subscribing. We'll be in touch soon.
 
We collect and use this information in accordance with our privacy policy.