Wolverine 4 is Bringing Multi-Tenancy to EF Core

I think that even in a crowded field of existing “mediator” tools, ASP.Net Core endpoint frameworks, and asynchronous messaging frameworks in .NET that Wolverine has a great opportunity to grow its user base this year. There’s a lot of value that Wolverine brings to the table that I don’t believe other tools do, but it’s been very focused on improving development in conjunction with Marten. With Wolverine 4.0, I’m hoping that much deeper support for EF Core will help Wolverine’s community grow by attracting more developers who aren’t necessarily using Marten (yet).

The “Critter Stack” (Marten and Wolverine) already has very strong support for multi-tenancy as you can see in some previous posts of mine:

And I think you get the point. Marten and Wolverine have a much more comprehensive feature set for multi-tenancy than any other persistence or messaging tool in the .NET ecosystem — in no small part because we seem to be the only community that cares about this somehow?

For Wolverine 4.0 (expected by June 1st) and in conjunction with a JasperFx Software customer, we’re adding first class multi-tenancy support for EF Core usage. Specifically, we’re aiming to allow users to use every bit of Wolverine’s existing multi-tenancy integration, transactional inbox/outbox support, and transactional middleware with EF Core while targeting a separate database for each tenant.

This work is in flight, but here’s a preview of the (working thank you) syntax. In bootstrapping, we need to tell Wolverine about both a main database for Wolverine storage and any tenant databases as in this sample from a web application:

        builder.Host.UseWolverine(opts =>
        {
            var mainConnectionString = builder.Configuration.GetConnectionString("main");

            opts.PersistMessagesWithSqlServer(mainConnectionString)

                // If you have a fixed number of tenant databases and it won't
                // change w/o downtime -- but don't worry, there are other options coming...
                .RegisterStaticTenants(x =>
                {
                    x.Register("tenant1", builder.Configuration.GetConnectionString("tenant1"));
                    x.Register("tenant2", builder.Configuration.GetConnectionString("tenant2"));
                    x.Register("tenant3", builder.Configuration.GetConnectionString("tenant]3"));
                });
            
            opts.Policies.AutoApplyTransactions();
            opts.Policies.UseDurableLocalQueues();
    
            TestingOverrides.Extension?.Configure(opts);
        });

Next, we need to tell Wolverine to make one or more of our EF Core DbContext types multi-tenanted using the databases we configured above like this:

        // Little sleight of hand, we're registering the DbContext with Wolverine,
        // but letting Wolverine deal with the connection string at runtime
        builder
            .Services
            .AddDbContextWithWolverineManagedMultiTenancy<ItemsDbContext>((b, connectionString) =>
            
                // Notice the specification of AutoCreate here, more in a second...
                b.UseSqlServer(connectionString), AutoCreate.CreateOrUpdate);

While Wolverine does support multi-tenancy tracking through both local message publishing and asynchronous messaging, let’s pretend our workflows start from an HTTP endpoint, so I’m going to add some basic tenant id detection using Wolverine.HTTP like so in our Program file:

var app = builder.Build();
app.MapWolverineEndpoints(opts =>
{
    // Set up tenant detection
    opts.TenantId.IsQueryStringValue("tenant");
    opts.TenantId.DefaultIs(StorageConstants.DefaultTenantId);
});

Alrighty then, here’s that simplistic little DbContext I’m using for testing right now:

public class ItemsDbContext : DbContext
{
    public ItemsDbContext(DbContextOptions<ItemsDbContext> options) : base(options)
    {
    }

    public DbSet<Item> Items { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Your normal EF Core mapping
        modelBuilder.Entity<Item>(map =>
        {
            map.ToTable("items", "mt_items");
            map.HasKey(x => x.Id);
            map.Property(x => x.Id).HasColumnName("id");
            map.Property(x => x.Name).HasColumnName("name");
            map.Property(x => x.Approved).HasColumnName("approved");
        });
    }
}

And finally, here’s a message handler that’s also doubling as a simplistic HTTP endpoint to create a new Item in our system by accepting a command:

public record StartNewItem(Guid Id, string Name);

public static class StartNewItemHandler
{
    [WolverinePost("/item")]
    public static IStorageAction<Item> Handle(StartNewItem command)
    {
        return new Insert<Item>(new Item
        {
            Id = command.Id, 
            Name = command.Name
        });
    }
}

For a little context, you might want to check out Wolverine’s storage side effects. Or with less “magic”, we could use this completely equivalent alternative:

public static class StartNewItemHandler
{
    [WolverinePost("/item")]
    public static void Handle(StartNewItem command, ItemsDbContext dbContext)
    {
        var item = new Item
        {
            Id = command.Id, 
            Name = command.Name
        };

        dbContext.Items.Add(item);
    }
}

Either way, Wolverine’s transactional middleware actually calls ItemsDbContext.SaveChangesAsync() for us and also deals with any necessary transactional outbox mechanics to “flush” outgoing messages after the transaction has succeeded.

Alright, let’s go to runtime, where Wolverine is going to happily handle a POST to /item by trying to find the tenant id out of a query string variable named “tenant”, then build an ItemsDbContext instance for us that’s pointed at the proper SQL Server database for the named tenant id. Our code up above doesn’t have to know anything about that process, we just have to write code to carry out the business requirements.

And that’s that! Of course there are plenty of other things to worry about and questions you might have, so let me try to anticipate those:

  • What about dynamically adding tenants without any downtime? Like Marten’s “Master Table Tenancy” model where you can add new tenants without any system down time and it “just works”, including Wolverine being able to spin up the necessary background agents for scheduled messages and whatnot? That’s definitely planned, and it will end up sharing quite a bit of code with the existing Marten support in this case and building on existing Wolverine capabilities
  • Will this work with Wolverine’s existing EF Core Saga support? Yes, or at least that’s planned and I’ll test that tomorrow
  • Does Wolverine’s transactional inbox/outbox support and scheduled messages extend to this new multi-tenancy model? Yes, that’s already working.
  • Can I use this with lightweight sagas? Yep, that too. Not working yet, but that will be in place by the end of tomorrow.
  • I’m spoiled by Marten and Wolverine’s ability to set up development databases on the fly, is there any chance of something like that for this EF Core integration? If you follow me on BlueSky (sorry), you might have seen me gripe about EF Core migrations as compared to (in my honest opinion) Marten’s much easier model for development time. After some griping and plenty of investigation, we will have at least a model where you can opt into having Wolverine use EF Core migrations to create any missing databases and apply any missing migrations to all the tenant databases at application startup. Having that capability is helping speed up the development of all of this.
  • Will the tenant database discovery be pluggable so we can use whatever existing mechanism we already have? Yes.
  • Can I use the Wolverine managed EF Core multi-tenancy outside of Wolverine message handlers or HTTP endpoints? That work hasn’t started yet, but that’s a particular need for JasperFx Software client work
  • If I’m using PostgreSQL and both Marten and EF Core, can we use both tools in the same application with multi-tenancy? Hopefully with just one set of configuration? Absolutely, yes.
  • Will there be a Wolverine powered equivalent to Marten’s “conjoined tenancy” model for multi-tenancy through one database? Um, not sure, probably not at first — but I’d be happy to talk to anyone who wants to volunteer for that pull request or wants to engage JasperFx to build that out!
  • When again? By June 1st.

2 thoughts on “Wolverine 4 is Bringing Multi-Tenancy to EF Core

    1. Trying to get there by wrapping some “it just works” stuff around EF Core. Won’t be quite as seamless as Marten of course. Still a work in progress.

Leave a reply to jeremydmiller Cancel reply