If you have read my blog articles, you will see that I am a huge fan of NServiceBus and have a considerable amount of experience with using it on projects. That said, I learned long ago in the rapidly changing world of software development that you need to reevaluate your options on regular basis. That framework or technology that didn't impress when you first looked at it might have matured while you were not looking and now it is a real contender.
I've checked out MassTransit a number of times in the past and I'm probably due to give it another look. It has just not compared with NServiceBus as far as features, documentation and support when I evaluated in the past.
Recently, I became aware of two other options in the .NET Service Bus space that I felt deserved a closer look. Rebus and Brighter. Since Rebus seemed to be similar to NServiceBus and MassTransit I started there.
Until a few weeks ago, I only had a vague awareness of Rebus. I had heard of it in passing and the reputation was that it needed time to mature. When I finally dug into Rebus, I was amazed at what I found. It is a feature rich opensource (free) framework that has options for professional support and even a solution for monitoring your Rebus solution.
Rebus is founded by Mogens Heller Grabe and according to one of his talks I found on YouTube there are about 100 contributors to Rebus. When I checked out the contributors in GitHub I found that while Mogens is definitely the primary contributor there is definitely other contributors. In that same video he explained that he wanted to make the configuration of Rebus as painless as possible and that it is a lightweight library vs a framework.
I have found that he nailed the painless configuration, but thankfully I would describe Rebus as a framework as it exists today. It is a framework in the sense that it is feature rich and supports multiple persistence and messaging platforms. The really good news is that Rebus leverages a set of componentized libraries to implement features. This means that each library is light and focused.
Another tenant of Mogen's vision is that Rebus is free opensource. Like many opensource projects, there is an option to purchase professional support and the Rebus monitoring solution Fleet Manager comes as part of the professional support contract. There are no published prices for Rebus Pro, and I will be interested in learning about what that pricing looks like.
I created a quick simple example project that is available on GitHub. Of course Rebus has a good set of example projects, but I built my own just as a way to explore the framework.
In my example, I really wanted to use the .NET Generic Host. I believe this is the best way to wire up a console application and seems to be the direction everything .NET is moving towards. There were no direct examples I could find to set up the .NET Generic Host for console applications, but it was relatively easy to figure it out.
Note that I'm using LocalStack running in Docker to provide the transport in my full example project you can find in GitHub. There is a docker-compose file at the root of that repo that you can use to fire up a mock of AWS SQS for the transport.
To get started, we need to wire up a host builder. There are two sections of the host builder below that are specific to Rebus.
This example is from the server side endpoint which consumes messages from the ServerMessages
queue and handles them with the appropriate message handler.
To register the handlers I used services.AutoRegisterHandlersFromAssemblyOf<HandleMessage>();
. This scans the assembly that contains one your message handlers you provide and registers all of the handlers in that assembly.
To configure Rebus, the AddRebus extension method is where most of the magic is. This is where you configure the logging and transport in my example. In more complex examples, this is where you will configure things like scale out, persistence, routing etc.
In my example I also included the AddHostedService<>
extension method. This wires up a hosted service which is not Rebus specific, but there is some important Rebus initialization code in that hosted service we will see in the next section.
private static async Task Main(string[] args)
{
//for localstack
var SqsConfig = new AmazonSQSConfig
{ UseHttp = true,
ServiceURL = "http://localhost:4566",
};
await Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
//where the Rebus activation happens
services.AddHostedService<ConsoleHostedService>();
// Automatically register all handlers
services
.AutoRegisterHandlersFromAssemblyOf<HandleMessage>();
//Configure Rebus
services.AddRebus(configure => configure
.Logging(l => l.ColoredConsole())
.Transport(t => t.UseAmazonSQS("ServerMessages"
, SqsConfig))
);
})
.RunConsoleAsync()
;
}
The block below is from the hosted service class ConsoleHostedService
. In the start method, it calls the UseRebus
extension method on the service provider to initialize Rebus. This was the part that gave me the most challenges, in creating my example.
Figuring out how to call UseRebus
when using the .NET Generic Host was a small challenge to work out as there were no examples for it. I ended up injecting IServiceProvider
into my ConsoleHostedService
class so that I could call UseRebus
.
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogDebug($"Starting service");
//Activate Rebus
_serviceProvider.UseRebus();
return Task.CompletedTask;
}
Routing is another important configuration component for endpoints that send messages. In my client endpoint, I am routing all of the messages in the assembly that contains the ImportantMessage
class to the ServierMessages
queue.
services.AddRebus(configure => configure
...
.Routing(r => r.TypeBased()
.MapAssemblyOf<ImportantMessage>("ServerMessages")));
Beyond a simple example, I think I would want to be a little more thoughtful on how this is arranged and I believe Rebus will support a more targeted approach.
Now we get to the exciting part where we put our business logic. The message handler is defined with the IHandleMessages<>
interface. In this case the handler will fire when the endpoint receives a message with the type of ImportantMessage
.
public class HandleMessage : IHandleMessages<ImportantMessage>
{
private readonly ILogger<HandleMessage> _log;
public HandleMessage(ILogger<HandleMessage> log)
{
_log = log;
}
public Task Handle(ImportantMessage message)
{
_log.LogInformation("Handled Important Message");
return Task.CompletedTask;
}
}
I was pleasantly surprised with my small Rebus experiment. I did not expect Rebus to be as full featured and easy to work with. In my opinion Mogen definitely nailed his goal of making Rebus as easy as possible to configure.
I think the only integration pattern I did not find a clear answer for in the Rebus libraries was Outbox. To be honest, I'm not sure that this is a real issue. There are a number of options for handling the challenges around distributed transactions that are probably better solutions that are less complex and more performant than Outbox.
I will definitely be considering Rebus for some future projects where I can get a better understanding of how it behaves in a production scenario.