Wolverine in C#/.NET: A Practical Guide with Code Examples, Uses, and Benefits

Wolverine is a .NET framework for command execution, message handling, and asynchronous messaging. It can be used as a simple in-process mediator, a local message bus, or as a full messaging framework when combined with infrastructure such as RabbitMQ.

The key idea is simple:

Application
โ†“
Command / Event
โ†“
Wolverine
โ†“
Handler
โ†“
Business Logic

When RabbitMQ is involved:

Service A
โ†“
Wolverine
โ†“
RabbitMQ
โ†“
Wolverine
โ†“
Service B Handler

1. What is Wolverine?

In a typical .NET application, you might have:

public async Task CreateProduct(CreateProductRequest request)
{
await productService.CreateProduct(request);
}

As the application grows, you may need:

  • Commands
  • Events
  • Background processing
  • RabbitMQ
  • Retries
  • Message routing
  • Request/reply communication
  • Multiple consumers

Managing all of this manually can introduce a lot of infrastructure code.

Wolverine provides a messaging and command-processing layer to simplify these scenarios. Messages can simply be normal C# classes or records; they don’t need to implement a special Wolverine interface.


2. Command and Handler

A command represents something that you want the application to do.

For example:

public record CreateProductCommand(
int ProductId,
string Name,
decimal Price);

Then create a handler:

public static class CreateProductHandler
{
public static async Task Handle(
CreateProductCommand command)
{
Console.WriteLine(
$"Creating product: {command.Name}");
await Task.CompletedTask;
}
}

There is no:

IHandler<CreateProductCommand>

or special base class required.

Wolverine discovers handler methods based on its conventions.


3. Configuring Wolverine

In an ASP.NET Core application:

using Wolverine;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseWolverine();
var app = builder.Build();
app.Run();

Now Wolverine is part of your application’s dependency-injection and hosting infrastructure.


4. Sending a Message

Wolverine provides IMessageBus.

For example:

public class ProductService
{
private readonly IMessageBus bus;
public ProductService(IMessageBus bus)
{
this.bus = bus;
}
public async Task CreateProduct()
{
await bus.SendAsync(
new CreateProductCommand(
100,
"Milk",
10.50m));
}
}

Wolverine finds the appropriate handler.

CreateProductCommand
โ†“
Wolverine
โ†“
CreateProductHandler
โ†“
Handle()

IMessageBus supports operations such as InvokeAsync(), SendAsync(), PublishAsync() and scheduled messages.


5. InvokeAsync vs SendAsync vs PublishAsync

This is an important distinction.

InvokeAsync

Use when you want to execute a handler and wait for it:

await bus.InvokeAsync(
new CreateProductCommand(
100,
"Milk",
10.50m));

You can also request a response:

var result =
await bus.InvokeAsync<ProductResult>(
new CreateProductCommand(
100,
"Milk",
10.50m));

SendAsync

Use when you want to send a message for processing:

await bus.SendAsync(
new CreateProductCommand(
100,
"Milk",
10.50m));

PublishAsync

Generally useful for events:

await bus.PublishAsync(
new ProductCreated(
100,
"Milk"));

The difference is mainly about the messaging semantics and whether you expect subscribers/handlers.


6. Wolverine with Dependency Injection

One useful feature is that handlers can receive services from .NET dependency injection.

For example:

public class CreateProductHandler
{
private readonly IProductRepository repository;
public CreateProductHandler(
IProductRepository repository)
{
this.repository = repository;
}
public async Task Handle(
CreateProductCommand command)
{
var product = new Product
{
Id = command.ProductId,
Name = command.Name,
Price = command.Price
};
await repository.AddAsync(product);
}
}

Wolverine creates the handler and resolves IProductRepository through the application’s DI container.


7. Wolverine + RabbitMQ

This is where Wolverine becomes particularly useful for microservices.

Suppose you have:

Product Service
|
| ProductCreated
โ†“
RabbitMQ
|
โ†“
Inventory Service

Without Wolverine, your application needs to work directly with the RabbitMQ client for things such as consuming messages, acknowledgements and message handling.

With Wolverine, RabbitMQ becomes the transport, while your application works primarily with C# messages and handlers.

Wolverine’s RabbitMQ integration uses the RabbitMQ .NET client underneath.

Install the RabbitMQ integration package:

dotnet add package WolverineFx.RabbitMQ

8. Configure RabbitMQ

For example:

builder.Host.UseWolverine(opts =>
{
opts.UseRabbitMq("amqp://localhost:5672")
.AutoProvision();
opts.ListenToRabbitQueue("product-events");
});

AutoProvision() can have Wolverine create the required RabbitMQ resources during startup.


9. Publish an Event to RabbitMQ

Define an event:

public record ProductCreated(
int ProductId,
string Name);

Publish it:

await bus.PublishAsync(
new ProductCreated(
100,
"Milk"));

Wolverine can route that message to the configured RabbitMQ destination.

Product Service
|
| ProductCreated
โ†“
Wolverine
|
โ†“
RabbitMQ
|
โ†“
Inventory Service

10. Consume the RabbitMQ Message

The receiving service can have:

public static class ProductCreatedHandler
{
public static async Task Handle(
ProductCreated message)
{
Console.WriteLine(
$"Product received: {message.ProductId}");
// Update inventory
await Task.CompletedTask;
}
}

The application doesn’t need to manually create a RabbitMQ consumer for this handler.

Wolverine receives the message and dispatches it to the appropriate handler.


11. Complete Example

Imagine an e-commerce system.

Product service

public record CreateProduct(
int Id,
string Name,
decimal Price);
public record ProductCreated(
int Id,
string Name);

Handler:

public class CreateProductHandler
{
private readonly IProductRepository repository;
private readonly IMessageBus bus;
public CreateProductHandler(
IProductRepository repository,
IMessageBus bus)
{
this.repository = repository;
this.bus = bus;
}
public async Task Handle(
CreateProduct command)
{
var product = new Product
{
Id = command.Id,
Name = command.Name,
Price = command.Price
};
await repository.AddAsync(product);
await bus.PublishAsync(
new ProductCreated(
product.Id,
product.Name));
}
}

The flow becomes:

CreateProduct
โ†“
Wolverine
โ†“
CreateProductHandler
โ†“
Database
โ†“
ProductCreated
โ†“
Wolverine
โ†“
RabbitMQ
โ†“
Inventory Service

12. Why is this better than directly using RabbitMQ?

Without Wolverine, you might write code around:

IConnection
IChannel
BasicPublishAsync()
BasicConsume()
BasicAckAsync()

Your business code then becomes mixed with messaging infrastructure.

With Wolverine:

await bus.PublishAsync(
new ProductCreated(product.Id, product.Name));

Your business logic focuses on what happened, while Wolverine handles much of the messaging infrastructure.


13. Retries and Failure Handling

Distributed systems fail.

For example:

Product Service
โ†“
RabbitMQ
โ†“
Inventory Service
โ†“
Database
โ†“
โŒ Database unavailable

You don’t necessarily want the message to simply disappear.

Wolverine provides error-handling policies, including retry behavior.

For example:

opts.Policies
.ForMessagesOfType<ProductCreated>()
.RetryWithCooldown(
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10));

Conceptually:

Message
โ†“
Handler
โ†“
Failure
โ†“
Retry after 1 sec
โ†“
Failure
โ†“
Retry after 5 sec
โ†“
Success

This is especially useful for transient failures such as temporary database or network problems.


14. Cascading Messages

A handler can also produce another message.

For example:

public static async Task<ProductCreated> Handle(
CreateProduct command)
{
// Save product...
return new ProductCreated(
command.Id,
command.Name);
}

The resulting message can be handled separately.

Wolverine supports cascading messages so one operation can trigger subsequent processing with its own handling/retry lifecycle.

This is useful for workflows such as:

Create Order
โ†“
Order Created
โ†“
Reserve Inventory
โ†“
Inventory Reserved
โ†“
Process Payment
โ†“
Payment Completed

15. Where Wolverine is Useful

Wolverine is particularly useful in:

Microservices

Order Service
โ†“
RabbitMQ
โ†“
Payment Service

Event-driven architecture

ProductCreated
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”
โ†“ โ†“
Inventory Search
Service Service

Background processing

Instead of making an HTTP request wait:

HTTP Request
โ†“
Create Job
โ†“
Return response
โ†“
Wolverine processes job

Asynchronous workflows

For example:

Order Created
โ†“
Validate Order
โ†“
Reserve Stock
โ†“
Take Payment
โ†“
Send Confirmation

Internal application communication

You can also use Wolverine without RabbitMQ:

Controller
โ†“
IMessageBus
โ†“
Handler
โ†“
Service

So you don’t have to introduce RabbitMQ just to benefit from Wolverine.


16. Major Benefits

BenefitExplanation
Less boilerplateLess messaging infrastructure code
Handler discoveryHandlers can be discovered automatically
RabbitMQ integrationEasy integration with RabbitMQ
RetriesBuilt-in error-handling policies
Async processingGood for background workloads
Message routingRoutes messages to appropriate destinations
Dependency InjectionWorks naturally with ASP.NET Core DI
Commands & eventsSupports both patterns
Request/replySupports responses from handlers
SchedulingMessages can be scheduled for later execution
Multiple transportsSupports RabbitMQ, Azure Service Bus, Kafka, Pulsar, AWS SQS and others
TestabilityHandlers can remain simple .NET methods

Wolverine supports multiple messaging transports and does not require handlers to be tied directly to a specific RabbitMQ queue or Kafka topic.


17. Wolverine vs Direct RabbitMQ

A useful way to think about it is:

RabbitMQ
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Messaging infrastructure
Queue
Exchange
Routing
Delivery
Broker

while:

Wolverine
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
.NET messaging framework
Handlers
Commands
Events
Routing
Retries
Message processing
Integration with RabbitMQ

So Wolverine doesn’t replace RabbitMQ.

Instead:

             Your C# Application
                     โ”‚
                     โ†“
                 Wolverine
                     โ”‚
                     โ†“
                  RabbitMQ
                     โ”‚
                     โ†“
             Other Services

RabbitMQ is the broker; Wolverine is the .NET messaging/application layer that works with it.


18. When Should You Use Wolverine?

Wolverine is a strong choice when your application needs:

  • Microservice-to-microservice communication
  • RabbitMQ or another message broker
  • Event-driven architecture
  • Background/asynchronous processing
  • Reliable message handling
  • Commands and events
  • Retries and error handling
  • Complex messaging workflows

For a small CRUD API that only performs synchronous database operations, Wolverine may be unnecessary complexity.

For a system such as:

ASP.NET Core API
โ†“
Wolverine
โ†“
RabbitMQ
โ†™ โ†˜
Order Inventory
Service Service
โ†“ โ†“
Database Database

it can significantly simplify the messaging layer.

In one sentence: Wolverine lets you write your .NET application around commands, events, and handlers, while it takes care of much of the plumbing required to execute and transport those messages reliably.

Leave a comment