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.

Wolverine on DotNetRocks

The fine folks at DotNetRocks graciously allowed me to come on and talk about Wolverine and its combination with Marten into the new “Critter Stack” for highly productive server side development in .NET.

Wolverine is a new framework (but based on previous tools dating back over a decade) for server side .NET development that acts as both an in-process mediator tool and message bus for asynchronous messaging between processes. In an admittedly crowded field, Wolverine stands apart from older tools in a couple important ways:

  • While Wolverine will happily take care of infrastructure concerns like error handling, logging, distributed tracing with OpenTelemetry, serialization, performance metrics, and interacting directly with message brokers, Wolverine does a much better job of keeping out of your application code
  • Wolverine’s runtime model — including its robust middleware strategy — completely bypasses the performance problems the older tools incur
  • Application code testability is a first class goal with Wolverine, and it shows. Unit testing is certainly easier with Wolverine keeping more infrastructure concerns out of your code, while also adding some unique test automation support for integration testing
  • Developer productivity is enhanced by aiming for baking in infrastructure setup and configuration directly into Wolverine. And because some of Wolverine’s productivity boost admittedly comes from coding convention magic, Wolverine can tell you exactly what it’s going to do at runtime through its built in diagnostics.

The usage is already going to change next week based on a lot of early user feedback, but you can easily get the gist of what Wolverine is like in usage through the JetBrains webinar on Wolverine a couple weeks back:

Marten and Friend’s (Hopefully) Big Future!

Marten was conceived and launched way back in 2016 as an attempt to quickly improve the performance and stability of a mission critical web application by utilizing Postgresql and its new JSON capabilities as a replacement for a 3rd party document database – and do that in a hurry before the next busy season. My former colleagues and I did succeed in that endeavor, but more importantly for the longer run, Marten was also launched as an open source project on GitHub and quickly attracted attention from other developers. The addition of an originally small feature set for event sourcing dramatically increased interest and participation in Marten. 

Fast forward to today, and we have a vibrant community of engaged users and a core team of contributors that are constantly improving the tool and discussing ideas about how to make it even better. The giant V4 release last year brought an overhaul of almost all the library internals and plenty of new capabilities. V5 followed early in 2022 with more multi-tenancy options and better tooling for development lifecycles and database management based on early issues with V4. 

At this point, I’d list the strong points of Marten that we’ve already achieved as:

  • A very useful document database option that provides the powerful developer productivity you expect from NoSQL solutions while also supporting a strong consistency model that’s usually missing from NoSQL databases. 
  • A wide range of viable hosting options by virtue of being on top of Postgresql. No cloud vendor lock-in with Marten!
  • Quite possibly the easiest way to build an application using Event Sourcing in .NET with both event storage and user defined view projections in the box
  • A great local development story through the simple ability to run Postgresql in a Docker container and Marten’s focus on an “it just works” style database schema management subsystem
  • The aforementioned core team and active user base makes Marten a viable OSS tool for teams wanting some reassurance that Marten is going to be well supported in the future

Great! But now it’s time to talk about the next steps we’re planning to take Marten to even greater heights in the forthcoming Marten V6 that’s being planned now. The overarching theme is to remove the most common hurdles for not choosing Marten. By and large, I think the biggest themes for Marten are:

  1. Scalability, so Marten can be used for much larger data sets. From user feedback, Marten is able to handle data sets of 10 million events today, but there’s opportunities to go far, far larger than that.
  2. Improvements to operational support. Database migrations when documents change, rebuilding projections without downtime, usage metrics, and better support for using multiple databases for multi-tenancy
  3. Marten is in good shape as a purely storage option for Event Sourcing, but users are very often asking for an array of subscription options to propagate events captured by Marten
  4. More powerful options for aggregating event data into more complex projected views
  5. Improving the Linq and other querying support is a seemingly never-ending battle
  6. The lack of professional support for Marten. Obviously a lot of shops and teams are perfectly comfortable with using FOSS tools knowing that they may have to roll up their sleeves and pitch in with support, but other shops are not comfortable with this at all and will not allow FOSS usage for critical functions. More on this later.

First though, Marten is getting a new “critter” friend in the larger JasperFx project family:

Wolverine is a new/old OSS command bus and messaging tool for .NET. It’s what was formerly being developed as Jasper, but the Marten team decided to rebrand the tool as a natural partner with Marten (both animals plus Weasel are members of the Mustelidae family). While both Marten and Wolverine are happily usable without each other, we think that the integration of these tools gives us the opportunity to build a full fledged platform for building applications in .NET using a CQRS architecture with Event Sourcing. Moreover, we think there’s a significant gap in .NET for this kind of tooling and we hope to fill that. 

So, onto future plans…

There’s a couple immediate ways to improve the scalability of Marten we’re planning to build in Marten V6. The first idea is to utilize Postgresql table sharding in a couple different ways. 

First, we can enable sharding on document tables based on user defined criteria through Marten configuration. The big challenge there is to provide a good migration strategy for doing this as it requires at least a 3 step process of copying the existing table data off to the side before creating the new tables. 

The next idea is to shard the event storage tables as well, with the immediate idea being to shard off of archived status to effectively create a “hot” storage of recent events and a “cold” storage of older events that are much less frequently accessed. This would allow Marten users to keep the active “hot” event storage to a much smaller size and therefore greatly improve potential performance even as the database continues to grow.

We’re not done “sharding” yet, but this time we need to shift to the asynchronous projection support in Marten. The core team has some ideas to improve the throughput of the asynchronous projection code as it is, but today it’s limited to only running on one single application node with “hot/cold” rollover support. With some help from Wolverine, we’re hoping to build a “sharded” asynchronous projection that can shard the processing of single projections and distribute the projection work across potentially many nodes as shown in the following diagram:

The asynchronous projection sharding is going to be a big deal for Marten all by itself, but there’s some other potentially big wins for Marten V6 with better tooling for projection rebuilds and asynchronous projections in general:

  1. Some kind of user interface to monitor and manage the asynchronous projections
  2. Faster projection rebuilds
  3. Zero downtime projection rebuilds

Marten + Wolverine == “Critter Stack” 

Again, both Marten and Wolverine will be completely usable independently, but we think there’s some potential synergy through the combination. One of the potential advantages of combining the tools is to use Wolverine’s messaging to give Marten a full fledged subscription model for Marten events. All told we’re planning three different mechanisms for propagating Marten events to the rest of your system:

  1. Through Wolverine’s transactional outbox right at the point of event capture when you care more about immediate delivery than strict ordering (this is already working)
  2. Through Martens asynchronous daemon when you do need strict ordering
  3. If this works out, through CDC event streaming straight from the database to Kafka/Pulsar/Kinesis

That brings me to the last topic I wanted to talk about in this post. Marten and Wolverine in their current form will remain FOSS under the MIT license, but it’s past time to make a real business out of these tools.

I don’t know how this is exactly going to work out yet, but the core Marten team is actively planning on building a business around Marten and now Wolverine. I’m not sure if this will be the front company, but I personally have formed a new company named “Jasper Fx Software” for my own activity – but that’s going to be limited to just being side work for at least awhile. 

The general idea – so far – is to offer:

  • Support contracts for Marten 
  • Consulting services, especially for help modeling and maximizing the usage of the event sourcing support
  • Training workshops
  • Add on products that add the advanced features I described earlier in this post

Maybe success leads us to offering a SaaS model for Marten, but I see that as a long way down the road.

What think you gentle reader? Does any of this sound attractive? Should we be focusing on something else altogether?