Choosing a service bus that meets our demands is a crucial part when developing a distributed system. There are many services to choose from like MSMQ, Azure Service Bus or RabbitMQ and even more frameworks that you can use in your projects as an additional layer of abstraction that makes your coding much easier when it comes to dealing with the specific service bus implementation. In this article, I’d like to present how to use the RabbitMQ in .NET Core with the help of really nice RawRabbit library.





At first, you may ask – why RabbitMQ? I’ve had some good experience with this type of service bus in the past. But it’s totally up to you which one to use – if you’re looking for a top-notch reliability, you might consider MSMQ instead (at least this is what the guys behind NServiceBus told me when I was asking them about their opinions on service buses).

Speaking of NServiceBus – it’s probably the most mature framework in the .NET world and I really recommend trying it out (yet it’s not totally for free).

For my case, I did pick up the RawRabbit – before I tried to use Rebus but it didn’t work with .NET Core back then. There’s also a raw implementation of RabbitMQ client that works with .NET core, but it takes much more effort to get everything up and running, which is the reason why the libraries like mentioned RawRabbit are being developed – to help us (programmers) start using such tool like RabbitMQ within our code by using a set of helpful methods and classes.

How to start using RawRabbit? Once you installed RabbitMQ on your machine, just create a new .NET Core application and update package.json with the latest version of this library e.g. “RawRabbit”: “1.10.0” and “RawRabbit.vNext”: “1.10.0” .

There’s also a very detailed documentation, yet I’ll present some of my code extensions that make usage of this library even easier.

This is quite easy to connect to RabbitMQ instance, simply by writing the following code:

var busConfig = new RawRabbitConfiguration { Username = "guest", Password = "guest", Port = 5672, VirtualHost = "/", Hostnames = { "localhost" } }; var busClient = BusClientFactory.CreateDefault(busConfig); 1 2 3 4 5 6 7 8 9 var busConfig = new RawRabbitConfiguration { Username = "guest" , Password = "guest" , Port = 5672 , VirtualHost = "/" , Hostnames = { "localhost" } } ; var busClient = BusClientFactory . CreateDefault ( busConfig ) ;

There are of course many more configuration properties, and you can even omit this part (passing busConfig into CreateDefault()) and use the default settings which are the same as the presented above.

Now, how do subscribe to some messages and also publish them? It’s also very easy task, but first, let me introduce two basic concepts:

Command – can be subscribed & processed only by a single consumer, produces Event.

Event – can be subscribed & processed by one or more consumers, may product another Command (saga and that sort of workflows).

Having this two important concepts in mind let’s define the following interfaces:

public interface ICommand { } public interface IEvent { } public interface ICommandHandler<in T> where T : ICommand { Task HandleAsync(T command); } public interface IEventHandler<in T> where T : IEvent { Task HandleAsync(T @event); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface ICommand { } public interface IEvent { } public interface ICommandHandler < in T > where T : ICommand { Task HandleAsync ( T command ) ; } public interface IEventHandler < in T > where T : IEvent { Task HandleAsync ( T @ event ) ; }

I’m not going to get into the details of CQ(R)S pattern. For dispatching the commands automatically via dependency injection (Autofac) I’m using the following code:

public interface ICommandDispatcher { Task DispatchAsync<T>(T command) where T : ICommand; } public class CommandDispatcher : ICommandDispatcher { private readonly IBusClient _bus; public CommandDispatcher(IBusClient bus) { _bus = bus; } public async Task DispatchAsync<T>(T command) where T : ICommand { if (command == null) throw new ArgumentNullException(nameof(command), "Command can not be null."); await _bus.PublishAsync(command); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public interface ICommandDispatcher { Task DispatchAsync < T > ( T command ) where T : ICommand ; } public class CommandDispatcher : ICommandDispatcher { private readonly IBusClient _bus ; public CommandDispatcher ( IBusClient bus ) { _bus = bus ; } public async Task DispatchAsync < T > ( T command ) where T : ICommand { if ( command == null ) throw new ArgumentNullException ( nameof ( command ) , "Command can not be null." ) ; await _bus . PublishAsync ( command ) ; } }

So, how do you subscribe to the message or publish a new one? Like this:

await bus.SubscribeAsync<T>(async (msg, context) => ...) //Perform some business logic on a received message. await bus.PublishAsync(message); 1 2 await bus . SubscribeAsync < T > ( async ( msg , context ) = > . . . ) //Perform some business logic on a received message. await bus . PublishAsync ( message ) ;

Ok, but how do we differentiate between the commands and events? The commands should be consumed only by a single consumer while the events may have many more consumers.

Take a look at the following code:

public static class RawRabbitExtensions { public static ISubscription SubscribeToCommand<TCommand>(this IBusClient bus, ICommandHandler<TCommand> handler, string name = null) where TCommand : ICommand => bus.SubscribeAsync<TCommand>(async (msg, context) => await handler.HandleAsync(msg), cfg => cfg.WithQueue(q => q.WithName(GetExchangeName<TCommand>(name)))); public static ISubscription SubscribeToEvent<TEvent>(this IBusClient bus, IEventHandler<TEvent> handler, string name = null) where TEvent : IEvent => bus.SubscribeAsync<TEvent>(async (msg, context) => await handler.HandleAsync(msg), cfg => cfg.WithQueue(q => q.WithName(GetExchangeName<TEvent>(name)))); private static string GetExchangeName<T>(string name = null) => string.IsNullOrWhiteSpace(name) ? $"{Assembly.GetEntryAssembly().GetName()}/{typeof(T).Name}" : $"{name}/{typeof(T).Name}"; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static class RawRabbitExtensions { public static ISubscription SubscribeToCommand < TCommand > ( this IBusClient bus , ICommandHandler < TCommand > handler , string name = null ) where TCommand : ICommand = > bus . SubscribeAsync < TCommand > ( async ( msg , context ) = > await handler . HandleAsync ( msg ) , cfg = > cfg . WithQueue ( q = > q . WithName ( GetExchangeName < TCommand > ( name ) ) ) ) ; public static ISubscription SubscribeToEvent < TEvent > ( this IBusClient bus , IEventHandler < TEvent > handler , string name = null ) where TEvent : IEvent = > bus . SubscribeAsync < TEvent > ( async ( msg , context ) = > await handler . HandleAsync ( msg ) , cfg = > cfg . WithQueue ( q = > q . WithName ( GetExchangeName < TEvent > ( name ) ) ) ) ; private static string GetExchangeName < T > ( string name = null ) = > string . IsNullOrWhiteSpace ( name ) ? $ "{Assembly.GetEntryAssembly().GetName()}/{typeof(T).Name}" : $ "{name}/{typeof(T).Name}" ; }

What does it do? Based on the message type (either command or event) it creates a new subscription. You may wonder why GetExchangeName() is not as trivial as .e.g returning just a typeof(T).Name? It turns out, that on the regular server this pattern works just fine, but once you put .NET Core application into Docker container it will get the same Assembly name as the other applications (e.g. “app” depending on the WORKDIR), therefore, this variation is required here to make it work correctly.

What eventually happens is that you will get the unique queues per instance of a different application that subscribes to the same event.

Whether you want to use here command/event handlers (like I did) is totally up to you.

Finally, within your application code, you may do the following:

bus.SubscribeToCommand<CreateUser>(); //Pass the handler via lambda expression or so. bus.SubscribeToEvent<UserCreated>(); bus.SubscribeToEvent<CartUpdated>(); 1 2 3 bus . SubscribeToCommand < CreateUser > ( ) ; //Pass the handler via lambda expression or so. bus . SubscribeToEvent < UserCreated > ( ) ; bus . SubscribeToEvent < CartUpdated > ( ) ;

The events subscribers will get their unique queues (e.g. you may have distinct microservices that react to the same events and they will all have their own queues) and the same will happen for the command subscribers – just make sure, that only a single application subscribes to the particular command type (there might be many instances of such application, though, and it won’t matter because of the same queue name).