Conventional Message Routing in Wolverine

I got a little feedback over the weekend that some folks newly encountering Wolverine for the first time think that it’s harder to use than some other tools because “it doesn’t do the message routing for you.” This fortunately isn’t true, but there’s obviously some work to do on improving documentation and samples to dispel that impression.

For the sake of this post, let’s assume that you want to use Wolverine for asynchronous messaging between processes using an external messaging broker transport (or using asynchronous messaging in a single application but queueing up work in an external message broker). And while Wolverine does indeed have support for interoperability with non-Wolverine applications, let’s assume that it’s going to be Wolverine on both sides of all the message pipes.

First though, just know that for all for external transports with Wolverine, the conventional routing is opt in, meaning that you have to explicitly turn it on when you configure Wolverine within the UseWolverine() bootstrapping. Likewise, know that you can also control exactly how Wolverine configures the listening or message sending behavior in the conventionally determined endpoints.

Now then, to just go fast and make Wolverine do all the message routing for you with predictable conventions “just” see these recipes:

For Wolverine’s Rabbit MQ integration, you can opt into conventional routing like this:

        using var host = await Host.CreateDefaultBuilder()
            .UseWolverine(opts =>
            {
                opts.UseRabbitMq()
                    // Opt into conventional Rabbit MQ routing
                    .UseConventionalRouting();
            }).StartAsync();

In this convention, message routing is to:

  1. Publish all messages to a Rabbit MQ exchange named after Wolverine’s message type name that’s effectively the alias for that message type. Most of the time that’s just the full name of the concrete .NET type.
  2. Create a Rabbit MQ queue named with Wolverine’s message type name for the message type of every known message handler within your application. Wolverine also configures a binding from a Rabbit MQ exchange with that same name to the Rabbit MQ queue for that message type.

This routing behavior was absolutely influenced by similar functionality in the older MassTransit framework. Imitation is the sincerest form of flattery, plus I just agree with what they did anyway. Wolverine adds additional value through its richer resource setup model (AutoProvision / AddResourceSetupOnStartup()) and stronger model of automatic handler discovery.

That’s the quickest possible way to get started, but you have plenty of other levers, knobs, and dials to farther control the conventions as shown below:

        using var host = await Host.CreateDefaultBuilder()
            .UseWolverine(opts =>
            {
                opts.UseRabbitMq()
                    // Opt into conventional Rabbit MQ routing
                    .UseConventionalRouting(c =>
                    {
                        // Override naming conventions
                        c.QueueNameForListener(type => type.Name);
                        c.ExchangeNameForSending(type => type.Name);

                        // Override the configuration for each listener
                        c.ConfigureListeners((l, _) =>
                        {
                            l.ProcessInline().ListenerCount(5);
                        });

                        // Override the sending configuration for each subscriber
                        c.ConfigureSending((s, _) =>
                        {
                            s.UseDurableOutbox();
                        });
                    })
                    
                    // Let Wolverine both discover all these necessary exchanges, queues,
                    // and binding, then also build them as necessary on the broker if
                    // they are missing
                    .AutoProvision();
            }).StartAsync();

The “extra” optional configuration I used above hopefully show you how to take more exacting control over the conventional routing, but the pure defaults in the first sample will help you get up and going fast.

Let’s move on.

Azure Service Bus

Wolverine’s Azure Service Bus integration comes with a pair of conventional routing options. The simpler is to let Wolverine create and utilize an Azure Service Bus queue named after each message type name for both sending the receiving with this syntax:

        using var host = await Host.CreateDefaultBuilder()
            .UseWolverine(opts =>
            {
                opts.UseAzureServiceBus("some connection string")
                    .UseConventionalRouting();
            }).StartAsync();

Slightly more complicated is another option to use Azure Service Bus topics and subscriptions like so:

    // opts is a WolverineOptions object    
    opts.UseAzureServiceBusTesting()
                
        .UseTopicAndSubscriptionConventionalRouting(convention =>
                {
                    // Optionally control every aspect of the convention and
                    // its applicability to types
                    // as well as overriding any listener, sender, topic, or subscription
                    // options
                });

In this usage:

  1. Outgoing messages are routed to an Azure Service Bus topic named after Wolverine’s message type name for that type
  2. At application startup, Wolverine will listen for an Azure Service Bus subscription for each message type from the application’s known message handlers. Likewise, that subscription will automatically be bound to a topic named after the message type name

As was the case with the Rabbit MQ conventions shown first, you have complete control over the naming conventions, the listener configuration, and the subscriber configuration.

For Wolverine’s AWS SQS integration, you can conventionally route to SQS queues named after Wolverine’s message type name (by default) like this:

        var host = await Host.CreateDefaultBuilder()
            .UseWolverine(opts =>
            {
                opts.UseAmazonSqsTransport()
                    .UseConventionalRouting();

            }).StartAsync();

As was the case with the Rabbit MQ conventions shown first, you have complete control over the naming conventions, the listener configuration, and the subscriber configuration.

Wolverine’s Kafka integration is a little bit different animal. In this case you can set up rules to publish to different Kafka topics by message type like this:

        using var host = await Host.CreateDefaultBuilder()
            .UseWolverine(opts =>
            {
                opts.UseKafka("localhost:29092").AutoProvision();

                opts.PublishAllMessages().ToKafkaTopics();

                opts.Services.AddResourceSetupOnStartup();
            }).StartAsync();

In this case, Wolverine will send each message type to a topic name derived from the message type that’s either Wolverine’s message type name or an explicitly configured topic name configured by attribute like:

[Topic("color.purple")]
public class PurpleMessage
{
}

What if I want a completely different routing convention?!?

There’s an extension point for programmatic message routing rules in Wolverine that’s utilized by all the capabilities shown above called IMessageRouteSource, but if you think you really want to use that, maybe just head to the Wolverine Discord room and we’ll try to help you out!

Summary

Wolverine has strong support for conventional message routing using external brokers, but you need to make at least a one line of code entry in your configuration to explicitly add this behavior to your system. In all cases, Wolverine is able to help build the necessary queues, topics, subscriptions, binding, and exchanges derived by these conventions in your message broker for an efficient developer experience. Moreover, you have complete power to fine tune the usage of this conventional routing for your application.