In this article, we’ll introduce the Wolverine library and its benefits for .NET developers. We’ll cover installation, setting up our first project, and core concepts around messaging. By the end, we’ll understand how to use Wolverine effectively in common practices and see real-world examples to help integrate it into our .NET applications.

To download the source code for this article, you can visit our GitHub repository.

Let’s get into it!

What is the Wolverine Library?

Wolverine is a framework for handling commands and asynchronous messaging in .NET. While other mature tools do similar things, Wolverine offers some unique benefits. It lets us write simple, clean code without forcing us to use complicated framework structures. Additionally, Wolverine is highly extensible, allowing developers to easily integrate custom functionalities and external infrastructure to meet specific application needs.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Getting Started with Wolverine

Let’s start building our first application using the Wolverine library. We implement a new API project using the command:

dotnet new webapi -n IntroductionToWolverineLibrary

This creates a new API project with the name IntroductionToWolverineLibrary.  Next, we install the Wolverine library:

dotnet add package WolverineFx

This installs the latest WolverineFx library to our project. At the time of writing this article, the most recent version is WolverineFx 2.15.2.

Following, in the Program.cs file, we need to add:

builder.Host.UseWolverine();

This command makes Wolverine’s usages discoverable throughout our entire project. We are now ready to start building our app while exploiting the Wolverine library.

Wolverine Library as a Mediator

For demonstration purposes, we build the project as a book order management system. First, let’s create a simple object for a new book order:

public record Order(Guid OrderId, string CustomerName, int BookId);

Here we define a new record Order, a special class type introduced in C# 9.0, designed for immutable data models. The object contains three fields. First, the OrderId, a Guid (Globally Unique Identifier) that identifies the order. Secondly, the CustomerName, a string represents the name of the customer. Lastly, the BookId, an int that identifies the book being ordered. 

InvokeAsync() To Send a Message

Now, let’s set up our first endpoint in the Program.cs file. We’ll construct each of our endpoints using minimal API syntax:

app.MapPost("/order", async (Order newOrder, IMessageBus bus) => await bus.InvokeAsync(newOrder))
    .WithName("NewBookOrder")
    .WithOpenApi();

Here, we define an HTTP POST endpoint at the route /order. We configure the endpoint to accept an Order object and an IMessageBus instance as parameters. Finally, the provided lambda function asynchronously executes the InvokeAsync() method on the bus, passing the newOrder as an argument.

The call to WithName("NewBookOrder") names this endpoint NewBookOrder, which can be useful for routing or documentation purposes.

Now, let’s build the receiver method of the message:

public class OrderHandler
{
    public static void Handle(Order? order) => Console.WriteLine($"New order received: {order}");
}

In a new class, we implement the method Handle that takes an Order object as a parameter and prints a message to the console, indicating that a new order has been received. By defining a method named Handle that takes an Order parameter, Wolverine recognizes this method as the handler for Order messages. When a new order message is received, Wolverine routes it to this method for processing using the .NET local message bus.

When Wolverine integrates into a .NET Core application, it registers the IMessageBus service in the underlying system IoC container, enabling its injection into controller classes or Minimal API endpoints, as demonstrated above. Then, the IMessageBus.InvokeAsync(message) method processes the provided message by determining the appropriate execution path for the message type and executing the corresponding Wolverine handler(s).

We don’t need to use interfaces to declare the intent of certain classes, such as IRequest or IHandler. Wolverine leverages Roslyn for Runtime Code Generation to minimize the code typically required to connect the correct handler to the appropriate command behind the scenes. Consequently, we can test the methods more easily. Instead of mocking an abstraction, we can directly test the method containing the business logic. Since the Handle method is a pure function, we can invoke and test it without dependencies.

Use of InvokeAsync<T>() With Return Object

In case we want our handler to return an object, we need to use the IMessageBus.InvokeAsync<T>() method:

app.MapPost("/orderReply", async (Order newOrder, IMessageBus bus) => 
    await bus.InvokeAsync<string>(newOrder))
    .WithName("NewBookOrderReply")
    .WithOpenApi();

By using the IMessageBus.InvokeAsync<T>(message) overload, we expect the handler to return an object of type T. In our example, we expect a simple string response. 

Now, let’s update the Handle method to return a response:

public static string Handle(Order? order) => $"New order received: {order}";

This method returns a confirmation string that we received a new order. It’s worth mentioning that we can modify the Handle method to return an object class or structure.

Using Queues with the Wolverine Library

The Wolverine library lets us use queues to send and receive our messages. In this way, we can communicate with different entities through a channel, like microservices or APIs.

Let’s set up the configuration to send messages to a specific queue:

builder.Host.UseWolverine( x =>
{
    x.PublishAllMessages().ToLocalQueue("book-ordering");
});

In the Program.cs file, we configure the messages to be transmitted through a local queue with the name book-ordering. We ensure that any message we send to Wolverine’s IMessageBus will be processed according to Wolverine’s configured rules.

Similarly, let’s create a new BookReview object:

public record BookReview(int BookId, string ReviewerName, int Rating);

This simple record holds information about a new book review. Now, let’s set up the new endpoint:

app.MapPost("/bookReview", async (BookReview review, IMessageBus bus) =>
{
    await bus.PublishAsync(review);

    return Results.Ok("Book review submitted successfully.");
})
.WithName("BookReviewEndpoint")
.WithOpenApi();

When we make a POST request to this endpoint, the application asynchronously publishes this review using Wolverine’s IMessageBus. After publishing the message, the endpoint returns an HTTP response indicating the successful submission of the book review. This setup allows for decoupled handling of book reviews, leveraging Wolverine’s capabilities for message-based communication and processing within the .NET application architecture.

We publish this message to all queue subscribers. This is a common best practice when we want to raise event-like messages. This is especially necessary in an architecture where we want to process messages asynchronously and independently from the transmitter.

Now, let’s proceed with the handler implementation:

public class BookReviewHandler
{
    public static void Handle(BookReview? review) => Console.WriteLine($"New book review received: {review}");
}

This handler is invoked after the message transmission since the method’s name is Handle and its argument is BookReview type. In this method, we similarly process the BookReview by generating a string that acknowledges receipt of the review.

Let’s check the logs in the console:

Successfully processed message IntroductionToWolverineLibrary.Models.BookReview#01909954-3efa-401e-b0ac-f93465bbadc4 from local://local-queue/

Wolverine produces a log when it successfully processes a message. As we can see here, the log indicates that Wolverine has successfully processed a message of type BookReview from the IntroductionToWolverineLibrary.Models namespace. The message source is the  local://local-queue/, indicating also that it is handled from a local queue within the application.

External Queues For Handling Messages

Wolverine provides the capability to use external queue providers to handle messaging. For example, we can integrate with popular messaging systems such as RabbitMQ, Apache Kafka, Azure Service Bus, and Amazon SQS. Subsequently, this allows Wolverine to leverage these platforms’ robust and scalable messaging infrastructures, enabling seamless asynchronous communication and distributed processing within our applications.

By utilizing Wolverine to both publish and process messages through an external infrastructure, we can handle complex messaging patterns, ensure reliable message delivery, and scale our messaging architecture to meet the demands of high-traffic environments.

Conclusion

In this article, we introduced the Wolverine library for handling messaging within our .NET application. We demonstrated, through an API how to set up endpoints, configure queues for message transmission, and implement handlers for asynchronous message processing.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!