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:
IConnectionIChannelBasicPublishAsync()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 SearchService 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
| Benefit | Explanation |
|---|---|
| Less boilerplate | Less messaging infrastructure code |
| Handler discovery | Handlers can be discovered automatically |
| RabbitMQ integration | Easy integration with RabbitMQ |
| Retries | Built-in error-handling policies |
| Async processing | Good for background workloads |
| Message routing | Routes messages to appropriate destinations |
| Dependency Injection | Works naturally with ASP.NET Core DI |
| Commands & events | Supports both patterns |
| Request/reply | Supports responses from handlers |
| Scheduling | Messages can be scheduled for later execution |
| Multiple transports | Supports RabbitMQ, Azure Service Bus, Kafka, Pulsar, AWS SQS and others |
| Testability | Handlers 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 infrastructureQueueExchangeRoutingDeliveryBroker
while:
Wolverineโโโโโโโโโโโโโโ.NET messaging frameworkHandlersCommandsEventsRoutingRetriesMessage processingIntegration 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 InventoryService 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.
