Multi-Tenancy: Mixed Modes with Marten

This is continuing a series about multi-tenancy with MartenWolverine, and ASP.Net Core:

  1. What is it and why do you care?
  2. Marten’s “Conjoined” Model
  3. Database per Tenant with Marten
  4. Mixed Modes with Marten (this post)

In previous posts I’ve described first why multi-tenancy might be useful, how to do it with Marten’s “conjoined” tenancy model, and lastly how to do it with separate databases for each tenant.

Marten is more flexible than that though, and it’s sometimes valid to mix and match the two tenancy modes. Offhand, I can think of a couple reasons that JasperFx clients or other Marten users have hit:

  1. Multi-level tenancy. In one case I’m aware of, a Marten system was using a separate database for each country where the system was operational, but used conjoined tenancy by company doing business within that country. That was beneficial for scaling, but also to be able to easily query across all businesses within that country and to keep reference data centralized by country database as well
  2. Being able to reduce hosting costs by centralizing one larger customer on their own database while keeping other customers on a second database. Or simply spreading customer data around to more equitably spread database load rather than incurring the hosting cost of having so many separate databases

In regards to the second point, I’d be curious to hear how that equation might change for something like the Neon serverless hosted PostgreSQL model.

The simplest model for this is the static multi-tenancy recipe in Marten that simply makes you specify the tenant ids and databases upfront like so:

        var builder = Host.CreateApplicationBuilder();
        builder.Services.AddMarten(opts =>
        {
            opts.MultiTenantedDatabases(tenancy =>
            {
                // Put data for these three tenants in the "main" database
                // The 2nd argument is just an identifier for logs and diagnostics
                tenancy.AddMultipleTenantDatabase(builder.Configuration.GetConnectionString("main"), "main")
                    .ForTenants("company1", "company2", "company3");

                // Put the single "big" tenant in the "group2" database
                tenancy.AddSingleTenantDatabase(builder.Configuration.GetConnectionString("group2"), "big");
            });
        });

        await builder.Build().StartAsync();

When fetching data, you still use the same mechanism to create a session for the tenant id like:

var session = store.LightweightSession("company1");

Marten will select the proper database for the “company1” tenant id above, and also set default filters on tenant_id = 'company1' on that session for all LINQ queries unless you explicitly tell Marten to query against all tenants or a different, specific tenant id.

Summary and What’s Next

You’ve got options with Marten! I think for the next post in this series I’ll move to Wolverine instead and how it can track work for a tenant across asynchronous message handling.

Leave a comment