Introducing Wolverine for Effective Server Side .NET Development

TL;DR — Wolverine’s runtime model is significantly different than other tools with similar functionality in the .NET world in a way that leads to simpler application code and more efficient runtime execution.

I was able to push a new version of Wolverine today based on the newly streamlined API worked out in this GitHub issue. Big thanks to Oskar, Eric, and Blake for their help in coming to what I feel turned out be a great improvement in usability — even though I took some convincing to get there. Also some huge thanks to Babu for the website scaffolding and publishing, and Khalid for all his graphics help and general encouragement.

The Wolverine docs — such as they are — are up on the Wolverine website.

In a nutshell, Wolverine is a mediator and message bus tool. There’s plenty of those tools already in the .NET space, so let me drop right into how Wolverine’s execution pipeline is genuinely unique and potentially does much more than older tools to improve developer productivity.

I’m going to build a very crude banking service that includes a message endpoint that will need to:

  1. Accept a message to apply a debit to a given account
  2. Verify that the debit amount is non-zero before you do anything else
  3. Load the information for the designated account from the database
  4. Apply the debit to the current balance of the account
  5. Persist the changes in balance back to the database

I’ll introduce “cascaded” messages tomorrow for things business rules like an account reaching a low balance or being overdrawn, but I’m ignoring that today to make this a smaller post.

While Wolverine supports EF Core and SQL Server as well, I unsurprisingly want to use Marten as a lower ceremony approach to application persistence in this particular case.

Before I even try to write the message handler, let me skip a couple design steps and say that I’m going to utilize three different sets of middleware to deal with cross cutting concerns:

  1. I’m going to use Wolverine’s built in Fluent Validation middleware to apply any known validation rules for the incoming messages. I’m honestly not sure I’d use this in real life, but this was built out and demo’d today as a way to demonstrate what’s “special” about Wolverine’s runtime architecture.
  2. Wolverine’s transactional & outbox middleware (duh)
  3. Custom middleware to load and push account data related to the incoming message to the message handler, or log and abort the message processing when the account data referenced in the message does not exist — and this more than anything is where the example code will show off Wolverine’s different approach. This example came directly from a common use case in a huge system at my work that uses NServiceBus.

Keeping in mind that we’ll be using some Wolverine middleware in a second, here’s the simple message handler to implement exactly the numbered list above:

public static class DebitAccountHandler
{
    // This explicitly adds the transactional middleware
    // The Fluent Validation middleware is applied because there's a validator
    // The Account argument is passed in by the AccountLookupMiddleware middleware
    [Transactional] // This could be done w/ a policy, but I'm opting to do this explicitly here
    public static void Handle(
        // The actual command message
        DebitAccount command, 
        
        // The current data for the account stored in the database
        // This will be "pushed" in by middleware
        Account account, 
        
        // The Marten document session service scoped to the 
        // current message being handled. 
        // Wolverine supports method injection similar to ASP.NET minimal api
        IDocumentSession session)
    {
        // decrement the balance
        account.Balance -= command.Amount;
        
        // Just telling Marten that this account document changed
        // so that it can be persisted by the middleware
        session.Store(account);
    }
}

I would argue that that handler method is very easy to understand. By removing away so many infrastructure concerns, a developer is able to mostly focus on business logic in isolation even without having to introduce all the baggage of some sort of hexagonal architecture style. Moreover, using Wolverine’s middleware allowed me to write purely synchronous code, which also reduces the code noise. Finally, but being able to “push” the business entity state into the method, I’m much more able to quickly write unit tests for the code and do TDD as I work.

Every message processing tool has middleware strategies for validation or transaction handling, but let’s take the example of the account data instead. When reviewing a very large system at work that uses NServiceBus, I noticed a common pattern of needing to load an entity from the database related to the incoming message and aborting the message handling if the entity does not exist. It’s an obvious opportunity for using middleware to eliminate the duplicated code.

First off, we need some way to “know” what the account id is for the incoming message. In this case I chose to use a marker interface just because that’s easy:

public interface IAccountCommand
{
    Guid AccountId { get; }
}

And the DebitAccountCommand becomes:

public record DebitAccount(Guid AccountId, decimal Amount) : IAccountCommand;

The actual middleware implementation is this:

// This is *a* way to build middleware in Wolverine by basically just
// writing functions/methods. There's a naming convention that
// looks for Before/BeforeAsync or After/AfterAsync
public static class AccountLookupMiddleware
{
    // The message *has* to be first in the parameter list
    // Before or BeforeAsync tells Wolverine this method should be called before the actual action
    public static async Task<(HandlerContinuation, Account?)> BeforeAsync(
        IAccountCommand command, 
        ILogger logger, 
        IDocumentSession session, 
        CancellationToken cancellation)
    {
        var account = await session.LoadAsync<Account>(command.AccountId, cancellation);
        if (account == null)
        {
            logger.LogInformation("Unable to find an account for {AccountId}, aborting the requested operation", command.AccountId);
        }
        
        return (account == null ? HandlerContinuation.Stop : HandlerContinuation.Continue, account);
    }
}

There’s also a Fluent Validation validator for the command as well (again, not sure I’d actually do it this way myself, but it shows off Wolverine’s middleware capabilities):

public class DebitAccountValidator : AbstractValidator<DebitAccount>
{
    public DebitAccountValidator()
    {
        RuleFor(x => x.Amount).GreaterThan(0);
    }
}

Stepping back to the actual application, I first added the WolverineFx.Marten Nuget reference to a brand new ASP.Net Core web api application, and made the following Program file to bootstrap the application:

using AppWithMiddleware;
using IntegrationTests;
using Marten;
using Oakton;
using Wolverine;
using Wolverine.FluentValidation;
using Wolverine.Marten;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMarten(opts =>
{
    // This would be from your configuration file in typical usage
    opts.Connection(Servers.PostgresConnectionString);
    opts.DatabaseSchemaName = "wolverine_middleware";
}).IntegrateWithWolverine()
    // Just letting Marten build out known database schema elements upfront
    .ApplyAllDatabaseChangesOnStartup();

builder.Host.UseWolverine(opts =>
{
    // Custom middleware to load and pass account data into message
    // handlers
    opts.Handlers.AddMiddlewareByMessageType(typeof(AccountLookupMiddleware));

    // This will register all the Fluent Validation validators, and
    // apply validation middleware where the command type has
    // a validator
    opts.UseFluentValidation();
});

var app = builder.Build();

// One Minimal API that just delegates directly to Wolverine
app.MapPost("/accounts/debit", (DebitAccount command, IMessageBus bus) => bus.InvokeAsync(command));

return await app.RunOaktonCommands(args);

After all of those pieces are put together, let’s finally talk about how Wolverine’s runtime execution is really different. Wolverine’s “special sauce” is that instead of forcing you to write your code wrapped around the framework, Wolverine conforms to your application code by generating code at runtime (don’t worry, it can be done ahead of time as well to minimize cold start time).

For example, here’s the runtime code that’s generated for the DebitAccountHandler.Handle() method:

// <auto-generated/>
#pragma warning disable
using FluentValidation;
using Microsoft.Extensions.Logging;
using Wolverine.FluentValidation;
using Wolverine.Marten.Publishing;

namespace Internal.Generated.WolverineHandlers
{
    // START: DebitAccountHandler1928499868
    public class DebitAccountHandler1928499868 : Wolverine.Runtime.Handlers.MessageHandler
    {
        private readonly FluentValidation.IValidator<AppWithMiddleware.DebitAccount> _validator;
        private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory;
        private readonly Wolverine.FluentValidation.IFailureAction<AppWithMiddleware.DebitAccount> _failureAction;
        private readonly Microsoft.Extensions.Logging.ILogger<AppWithMiddleware.DebitAccount> _logger;

        public DebitAccountHandler1928499868(FluentValidation.IValidator<AppWithMiddleware.DebitAccount> validator, Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory, Wolverine.FluentValidation.IFailureAction<AppWithMiddleware.DebitAccount> failureAction, Microsoft.Extensions.Logging.ILogger<AppWithMiddleware.DebitAccount> logger)
        {
            _validator = validator;
            _outboxedSessionFactory = outboxedSessionFactory;
            _failureAction = failureAction;
            _logger = logger;
        }



        public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation)
        {
            await using var documentSession = _outboxedSessionFactory.OpenSession(context);
            var debitAccount = (AppWithMiddleware.DebitAccount)context.Envelope.Message;
            (var handlerContinuation, var account) = await AppWithMiddleware.AccountLookupMiddleware.BeforeAsync((AppWithMiddleware.IAccountCommand)context.Envelope.Message, ((Microsoft.Extensions.Logging.ILogger)_logger), documentSession, cancellation).ConfigureAwait(false);
            if (handlerContinuation == Wolverine.HandlerContinuation.Stop) return;
            Wolverine.FluentValidation.Internals.FluentValidationExecutor.ExecuteOne<AppWithMiddleware.DebitAccount>(_validator, _failureAction, debitAccount);
            AppWithMiddleware.DebitAccountHandler.Handle(debitAccount, account, documentSession);

            // Commit the unit of work
            await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false);
        }

    }

    // END: DebitAccountHandler1928499868


}

It’s auto-generated code, so it’s admittedly ugly as sin, but there’s some things I’d like you to notice or just no:

  • This class is only instantiated one single time in the application and held in memory for the rest of the application instance’s lifecycle
  • That handler code is managing service lifecycle and service disposal, but yet there’s no IoC tool anywhere in sight
  • The code only has to create and resolve the Fluent Validation validator one single time, and it’s inlined access the complete rest of the way
  • The code up above minimizes the number of objects allocated per message being handled compared to other tools that utilize some kind of Russian Doll middleware model by dodging the need for framework adapter objects that are created and destroyed for each message execution. Less garbage collector thrashing from fewer object allocations means better performance and scalability
  • The Fluent Validation middleware wouldn’t even apply to message types that don’t have any known validators, and can optimize itself. Contrast that with Fluent Validation middleware strategies with something like MediatR where it would create a decorator object for each request and loop through an empty enumerable even when there are not validator for a given message type. That’s not a lot of overhead per se, but that adds up when there’s a lot of framework cruft.
  • You might have to take my word for it, but having built other frameworks and having spent a long time poring over the internals of other similar frameworks, Wolverine is going to do a lot fewer object allocations, indirections, and dictionary lookups at runtime than other tools with similar capabilities

I’m following this up immediately tomorrow by adding some “cascading” messages and diving into the built in testing support within Wolverine.

Advertisement

2 thoughts on “Introducing Wolverine for Effective Server Side .NET Development

  1. “There’s also a Fluent Validation validator for the command as well (again, not sure I’d actually do it this way myself, but it shows off Wolverine’s middleware capabilities)”

    How would you do it? What would be your prefered way?

    1. For commands, especially if they’re smaller, I might just do it explicitly right in the message handler. I just felt as I was whipping up the fluent validation samples that it felt like a lot of ceremony just to do something simple. I think fluent validation makes a lot more sense where you’re validating user input from forms and needing to apply those validation errors to a UI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s