It’s been a little bit since I’ve written any kind of update on the unofficial “Critter Stack” roadmap, with the last update in February. A ton of new, important strategic features have been added to especially Marten since then, with plenty of expansion of Wolverine to boot. Before jumping into what’s to come, let me indulge in a bit of retrospective about what new features or improvements have been delivered in 2024 so far before getting into the road map in the next section.
More parsimonious usage of database connections for better scalability and improved integration with GraphQL via Hot Chocolate
Some ability to do zero down time deployments event with asynchronous projections
Blue/green deployment capabilities for “write” model projections (that’s a big deal)
Ability to add new tenanted databases at runtime for both Marten and Wolverine with zero downtime
There’s only one user so far, but CritterStackPro.Projections is the first paid add on model for Marten & Wolverine that allows for better load distribution of asynchronous projections and event subscriptions within a clustered application
Marten has first class support for strong typed identifiers now — which was a long requested feature that got put off because of how much effort I feared that would require (rightly as it turned out, but it’s all done now)
At this point I feel like we’ve crossed off the mass majority of the features I thought we needed to add to Marten this year to be able to stand Marten up against basically any other event store infrastructure tooling on the whole damn planet. What that also means is that I think that Marten development probably slows down to nothing but bug fixes and community contributions as folks run into things. There are still some features in the backlog that I might personally work on, but that will be in the course of some ongoing and potential JasperFx client work.
That being said, let’s talk about the rest of the year!
The Roadmap for the Back Half of 2024
Obviously, this roadmap is just a snapshot in time and client needs, community requests, and who knows what changes from Microsoft or other related tools could easily change priorities from any of this. All that being said, this is the Critter Stack core team & I’s current vision of the next big steps.
RavenDb integration with Wolverine. This is some client sponsored work that I’m hoping will set Wolverine up for easier integration with other database engines in the near future
“Critter Watch” — an ongoing effort to build out a management and monitoring console application for any combination of Marten, Wolverine, and future critters. This will be a paid product. We’ve already had a huge amount of feedback from Marten & Wolverine users, and I’m personally eager to get this moving in the 3rd quarter
Marten 8.0 and Wolverine 4.0 — the goal here is mostly a rearrangement of dependencies underneath both Marten & Wolverine to eliminate duplication and spin out a lot of the functionality around projections and the async daemon. This will also be a significant effort to spin off some new helper libraries for the “Critter Stack” to enable the next bullet point
“Ermine” — a port of Marten’s event store capabilities and a subset of its document database capabilities to SQL Server. My thought is that this will share a ton of guts with Marten. I’m voting that Ermine will have direct integration with Wolverine from the very beginning as well for subscriptions and middleware similar to the existing Wolverine.Marten integration
If Ermine goes halfway well, I’d love to attempt a CosmosDb and maybe a DynamoDb backed event store in 2025
As usual, that list is a guess and unlikely to ever play out exactly that way. All the same though, there’s my hopes and dreams for the next 6 months or so.
Did I miss something you were hoping for? Does any of that concern you? Let me and the rest of the Critter Stack community know either here or anywhere in our Discord room!
There’s been a definite theme lately about increasing the performance and scalability of Marten, as evident (I hope) in my post last week describing new optimization options in Marten 7.25. Today I was able to push a follow up feature that got missed in that release that allows Marten users to utilize PostgreSQL table partitioning behind the scenes for document storage (7.25 added a specific utilization of table partitioning for the event store). The goal here is in selected scenarios, this would enable PostgreSQL to be mostly working with far smaller tables than it would otherwise, and hence perform better in your system.
Think of these common usages of Marten:
You’re using soft deletes in Marten against a document type, and the mass majority of the time Marten is putting a default filter in for you to only query for “not deleted” documents
You are aggressively using the Marten feature to mark event streams as archived when whatever process they model is complete. In this case, Marten is usually querying against the event table using a value of is_archived = false
You’re using “conjoined” multi-tenancy within a single Marten database, and most of the time your system is naturally querying for data from only one tenant at a time
Maybe you have a table where you’re frequently querying against a certain date property or querying for documents by a range of expected values
In all of those cases, it would be more performant to opt into PostgreSQL table partitioning where PostgreSQL is separating the storage for a single, logical table into separate “partition” tables. Again, in all of those cases above we can enable PostgreSQL + Marten to largely be querying against a much smaller table partition than the entire table would be — and querying against smaller database tables can be hugely more performant than querying against bigger tables.
The Marten community has been kicking around the idea of utilizing table partitioning for years (since 2017 by my sleuthing last week through the backlog), but it always got kicked down the road because of the perceived challenges in supporting automatic database migrations for partitions the same way we do today in Marten for every other database schema object (and in Wolverine too for that matter).
Thanks to an engagement with a JasperFx customer who has some pretty extreme scalability needs, I was able to spend the time last week to break through the change management challenges with table partitioning, and finally add table partitioning support for Marten.
As for what’s possible, let’s say that you want to create table partitioning for a certain very large table in your system for a particular document type. Here’s the new option for 7.26:
var store = DocumentStore.For(opts =>
{
opts.Connection("some connection string");
// Set up table partitioning for the User document type
opts.Schema.For<User>()
.PartitionOn(x => x.Age, x =>
{
x.ByRange()
.AddRange("young", 0, 20)
.AddRange("twenties", 21, 29)
.AddRange("thirties", 31, 39);
});
// Or use pg_partman to manage partitioning outside of Marten
opts.Schema.For<User>()
.PartitionOn(x => x.Age, x =>
{
x.ByExternallyManagedRangePartitions();
// or instead with list
x.ByExternallyManagedListPartitions();
});
// Or use PostgreSQL HASH partitioning and split the users over multiple tables
opts.Schema.For<User>()
.PartitionOn(x => x.UserName, x =>
{
x.ByHash("one", "two", "three");
});
opts.Schema.For<Issue>()
.PartitionOn(x => x.Status, x =>
{
// There is a default partition for anything that doesn't fall into
// these specific values
x.ByList()
.AddPartition("completed", "Completed")
.AddPartition("new", "New");
});
});
To use the “hot/cold” storage on soft-deleted documents, you have this new option:
var store = DocumentStore.For(opts =>
{
opts.Connection("some connection string");
// Opt into partitioning for one document type
opts.Schema.For<User>().SoftDeletedWithPartitioning();
// Opt into partitioning and and index for one document type
opts.Schema.For<User>().SoftDeletedWithPartitioningAndIndex();
// Opt into partitioning for all soft-deleted documents
opts.Policies.AllDocumentsSoftDeletedWithPartitioning();
});
storeOptions.Policies.AllDocumentsAreMultiTenantedWithPartitioning(x =>
{
// Selectively by LIST partitioning
x.ByList()
// Adding explicit table partitions for specific tenant ids
.AddPartition("t1", "T1")
.AddPartition("t2", "T2");
// OR Use LIST partitioning, but allow the partition tables to be
// controlled outside of Marten by something like pg_partman
// https://github.com/pgpartman/pg_partman
x.ByExternallyManagedListPartitions();
// OR Just spread out the tenant data by tenant id through
// HASH partitioning
// This is using three different partitions with the supplied
// suffix names
x.ByHash("one", "two", "three");
// OR Partition by tenant id based on ranges of tenant id values
x.ByRange()
.AddRange("north_america", "na", "nazzzzzzzzzz")
.AddRange("asia", "a", "azzzzzzzz");
// OR use RANGE partitioning with the actual partitions managed
// externally
x.ByExternallyManagedRangePartitions();
});
Summary
Your mileage will vary of course depending on how big your database is and how you really query the database, but at least in some common cases, the Marten community is pretty excited for the potential of table partitioning to improve Marten performance and scalability.
Just a reminder, JasperFx Software offers support contracts and consulting services to help you get the most out of the “Critter Stack” tools (Marten and Wolverine).If you’re building server side applications on .NET, the Critter Stack is the most feature rich tool set for Event Sourcing and Event Driven Architectures around.
The theme of the last couple months for the Marten community and I has been a lot of focus on improving Marten’s event sourcing feature set to be able to reliably handle very large data loads. With that being said, Marten 7.25 was released today with a huge amount of improvements around its performance, scalability, and reliability under very heavy loads (we’re talking about databases with hundreds of millions of events).
Before I get into the details, there’s a lot of thanks and credit to go around:
Core team member JT made several changes to reduce the amount of object allocations that Marten does at runtime in SQL generation — and basically every operation it does involves SQL generation
Ben Edwards contributed several ideas, important feedback, and some optimization pull requests toward this release
Babu made some improvements to our CI pipeline that made it a lot easier for me to troubleshoot the work I was doing
a-shtifanov-laya did some important load testing harness work that helped quite a bit to validate the work in this release
Urbancsik Gergely for doing a lot of performance and load testing with Marten that helped tremendously
And I’ll be giving some personal thanks to a couple JasperFx clients who enabled me to spend so much time on this effort
And now, the highlights for event store performance, scalability, and reliability improvements — most of which are “opt in” configuration items so as to not disturb existing users:
The new “Quick Append” option is completely usable and appears from testing to be about 2X as fast as the V4-V7 “Rich” appending process. More than that, opting into the quick append mechanism appears to eliminate the event “skipping” problem with asynchronous projections or event subscriptions that some people have experienced in very heavy loads. Lastly, I originally meant to play this work because I think it will alleviate issues that some people run into with concurrent operations trying to append events to the same event streams
Marten can create a Hot/Cold Storage mechanism around its event store by leveraging PostgreSQL native table partitioning. There’s work on users part to mark event streams as archived for this to matter, but this is potentially a huge win for Marten scalability. A later Marten release will shortly add partitioning support to Marten document tables
There’s several optimizations inside of even the classic, “rich” event appending that reduce the number of network round trips happening at runtime — and thats a good thing because network round trips are evil!
Outside of the event store improvements, Marten also got a new “Specification” alternative called “query plans” for reusable query logic for when Marten’s compiled query feature won’t work. The goal with this feature is to help a JasperFx client migrate off of Clean Architecture style repository wrapper abstractions in a way that doesn’t cause code duplication while also setting them up to utilize Marten’s batch query feature for a much more performant code.
Summary
I’m still digging out from a very good family vacation, but man, getting this stuff out feels really good. The Marten community is very vibrant right now, with a lot of community engagement that’s driving the tool’s capabilities into much more serious system territory. The “hot/cold storage” feature that just went in has been in the Marten backlog since 2017, and I’m thrilled to finally see that make it in.
As Houston gets drenched by Hurricane Beryl as I write this, I’m reminded of a formative set of continuing education courses I took when I was living in Houston in the late 90’s and plotting my formal move into software development. Whatever we learned about VB6 in those MSDN classes is long, long since obsolete, but one pithy saying from one of our instructors (who went on to become a Marten user and contributor!) stuck with me all these years later:
His point then, and my point now quite frequently working with JasperFx Software clients, is that round trips between browsers to backend web servers or between application servers and the database need to be treated as expensive operations and some level of request, query, or command batching is often a very valuable optimization in systems design.
Consider my family’s current kitchen predicament as diagrammed above. The very expensive, original refrigerator from our 20 year old house finally gave up the ghost, and we’ve had it completely removed while we wait on a different one to be delivered. Fortunately, we have a second refrigerator in the garage. When cooking now though, it’s suddenly a lot more time consuming to go to the refrigerator for an ingredient since I can’t just turn around and grab something when the kitchen refrigerator was just a step away. Now that we have to walk across the house from the kitchen to the garage to get anything from the other refrigerator, it’s becoming very helpful to try to grab as many things as you can at one time so you’re not constantly running back and forth.
While this issue certainly arises from user interfaces or browser applications making a series of little requests to a backing server, I’m going to focus on database access for the rest of this post. Using a simple example from Marten usage, consider this code where I’m just creating five little documents and persisting them to a database:
public static async Task storing_many(IDocumentSession session)
{
var user1 = new User { FirstName = "Magic", LastName = "Johnson" };
var user2 = new User { FirstName = "James", LastName = "Worthy" };
var user3 = new User { FirstName = "Michael", LastName = "Cooper" };
var user4 = new User { FirstName = "Mychal", LastName = "Thompson" };
var user5 = new User { FirstName = "Kurt", LastName = "Rambis" };
session.Store(user1);
session.Store(user2);
session.Store(user3);
session.Store(user4);
session.Store(user5);
// Marten will *only* make a single database request here that
// bundles up "upsert" statements for all five users added above
await session.SaveChangesAsync();
}
In the code above, Marten is only issuing a single batched command to the backing database that performs all five “upsert” operations in one network round trip. We were very performance conscious in the very early days of Marten development and did quite a bit of experimentation with different options for JSON serialization or how exactly to write SQL that queried inside of JSONB or even table structure. Consistently and unsurprisingly though, the biggest jump in performance was when we introduced command batching to reduce the number of network round trips between code using Marten and the backing PostgreSQL database. That early performance testing also led us to early investments in Marten’s batch querying support and the Include() query functionality that allows Marten users to fetch related data with fewer network hops to the database.
Just based on my own experience, here are two trends I see about interacting with databases in real world systems:
There’s a huge performance gain to be made by finding ways to batch database queries
It’s very common for systems in the real world to suffer from performance problems that can at least partially be traced to unnecessary chattiness between an application and its backing database(s)
At a guess, I think the underlying reasons for the chattiness problem are something like:
Developers who just aren’t aware of the expense of network round trips or aren’t aware of how to utilize any kind of database query batching to reduce the problems
Wrapper abstractions around the raw database persistence tooling that hides more powerful APIs that might alleviate the chattiness problem
Wrapper abstractions that encourage a pattern of only loading data by keys one row/object/document at a time
Wrapper abstractions around the raw database persistence that discourage developers from learning more about the underlying persistence tooling they’re using. Don’t underestimate how common that problem is. And I’ve absolutely been guilty of causing that issue as a younger “architect” in the past who created those abstractions.
Complicated architectural layering that can make it quite difficult to easily reason about the cause and effect between system inputs and the database queries that those inputs spawn. Big call stacks of a controller calling a mediator tool that calls one service that calls other services that call different repository abstractions that all make database queries is a common source of chattiness because it’s hard to even see where all the chattiness is coming from by reading the code.
As you might know if you’ve stumbled across any of my writings or conference talks from the last couple years, I’m not a big fan of typical Clean/Onion Architecture approaches. I think these approaches introduce a lot of ceremony code into the mix that I think causes more harm overall than whatever benefits they bring.
Here’s an example that’s somewhat contrived, but also quite typical in terms of the performance issues I do see in real life systems. Let’s say you’ve got a command handler for a ShipOrder command that will need to access data for both a related Invoice and Order entity that could look something like this:
public class ShipOrderHandler
{
private readonly IInvoiceRepository _invoiceRepository;
private readonly IOrderRepository _orderRepository;
private readonly IUnitOfWork _unitOfWork;
public ShipOrderHandler(
IInvoiceRepository invoiceRepository,
IOrderRepository orderRepository,
IUnitOfWork unitOfWork)
{
_invoiceRepository = invoiceRepository;
_orderRepository = orderRepository;
_unitOfWork = unitOfWork;
}
public async Task Handle(ShipOrder command)
{
// Making one round trip to get an Invoice
var invoice = await _invoiceRepository.LoadAsync(command.InvoiceId);
// Then a second round trip using the results of the first pass
// to get follow up data
var order = await _orderRepository.LoadAsync(invoice.OrderId);
// do some logic that changes the state of one or both of these entities
// Commit the transaction that spans the two entities
await _unitOfWork.SaveChangesAsync();
}
}
The code is pretty simple in this case, but we’re still making more database round trips than we absolutely have to — and real enterprise systems can get much, much bigger than my little contrived example and incur a lot more overhead because of the chattiness problem that the repository abstractions naturally let in.
Let’s try this functionality again, but this time just depending on the raw persistence tooling (Marten’s IDocumentSession and use a Wolverine-style command handler to boot to further reduce the code noise:
public static class ShipOrderHandler
{
// We're still keeping some separation of concerns to separate the infrastructure from the business
// logic, but Wolverine lets us do that just through separate functions instead of having to use
// all the limiting repository abstractions
public static async Task<(Order, Invoice)> LoadAsync(IDocumentSession session, ShipOrder command)
{
// This is important (I think:)), the admittedly complicated
// Marten usage below fetches both the invoice and its related order in a
// single network round trip to the database and can lead to substantially
// better system performance
Order order = null;
var invoice = await session
.Query<Invoice>()
.Include<Order>(i => i.OrderId, o => order = o)
.Where(x => x.Id == command.InvoiceId)
.FirstOrDefaultAsync();
return (order, invoice);
}
public static void Handle(ShipOrder command, Order order, Invoice invoice)
{
// do some logic that changes the state of one or both of these entities
// I'm assuming that Wolverine is handling the transaction boundaries through
// middleware here
}
}
In the second code sample, we’ve been able to go right at the Marten tooling to take advantage of its more advanced functionality to batch up data fetching for better performance that wasn’t easily possible when we were putting repository abstractions between our command handler and the underlying persistence tooling. Moreover, we can even reason about the resulting database operations that are happening as a result of our command that can be somewhat obfuscated by more layers and more code separation as is common in Onion/Clean/Ports and Adapters style approaches.
It’s not just repository abstractions that cause problems, sometimes it’s just happily useful little extension methods that can be the source of chattiness. Here’s a pair of helper extension methods around Marten’s event store functionality that help you start a new event stream in a single line of code or append a single event to an existing event stream in a single line of code:
public static class DocumentSessionExtensions
{
public static Task Add<T>(this IDocumentSession documentSession, Guid id, object @event, CancellationToken ct)
where T : class
{
documentSession.Events.StartStream<T>(id, @event);
return documentSession.SaveChangesAsync(token: ct);
}
public static Task GetAndUpdate<T>(
this IDocumentSession documentSession,
Guid id,
int version,
// If we're being finicky about performance here, these kinds of inline
// lambdas are NOT cheap at runtime and I'm recommending against
// continuation passing style APIs in application hot paths for
// my clients
Func<T, object> handle,
CancellationToken ct
) where T : class =>
documentSession.Events.WriteToAggregate<T>(id, version, stream =>
stream.AppendOne(handle(stream.Aggregate)), ct);
}
Fine, right? These potentially make your code cleaner and simpler but of course, they’re also potentially harmful. Here’s an example of these two extension methods that were similar to some code I saw in the wild last week:
public static class Handler
{
public static async Task Handle(Command command, IDocumentSession session, CancellationToken token)
{
var id = CombGuidIdGeneration.NewGuid();
// One round trip
await session.Add<Aggregate>(id, new FirstEvent(), token);
if (command.SomeCondition)
{
// This actually makes a pair of round trips, one to fetch the current state
// of the Aggregate compiled from the first event appended above, then
// a second to append the SecondEvent
await session.GetAndUpdate<Aggregate>(id, 1, _ => new SecondEvent(), token);
}
}
}
I got involved with this code in reaction to some load testing that was resulting in disappointing results. When I was pulled in, I saw the extra round trips that snuck in because of the usage of the convenience extension methods they had been using, and suggested a change to something like this (but with Wolverine’s aggregate handler workflow that simplified the code more than this):
public static class Handler
{
public static async Task Handle(Command command, IDocumentSession session, CancellationToken token)
{
var events = determineEvents(command).ToArray();
var id = CombGuidIdGeneration.NewGuid();
session.Events.StartStream<Aggregate>(id, events);
await session.SaveChangesAsync(token);
}
// This was isolated so you can easily unit test the business
// logic that "decides" what events to append
public static IEnumerable<object> determineEvents(Command command)
{
yield return new FirstEvent();
if (command.SomeCondition)
{
yield return new SecondEvent();
}
}
}
The code above cut down the number of network round trips to the database and greatly improved the results of the load testing.
Summary
If system performance is a concern in your system (it’s not always), you probably need to be cognizant of how chatty your application is in regards to its communication and interaction with the backing database. Or any other remote system or infrastructure that your system interacts with at runtime.
Personally, I think that higher ceremony code structures make it much more likely to incur issues with database chattiness especially by first obfuscating your code so you don’t even easily recognize where there’s chattiness, then second by wrapping simplifying abstractions around your database persistence tooling that eliminate the usage of more advanced functionality for query batching.
And of course, both Wolverine and Marten put a heavy emphasis on reducing code ceremony and generally on code noise in general because I personally think that’s very valuable to help teams succeed over time with software systems in the wild. My theory of the case is that even at the cost of a little bit of “magic”, simply reducing the amount of code you have to wade through in existing systems will make those systems easier to maintain and troubleshoot over time.
And on that note, I’m basically on vacation for the next week, and you can address your complaints about my harsh criticism of Clean/Onion Architectures to the ether:-)
I’m a huge fan of alt country or Americana music, definitely appreciate some classic country like Johnny Cash, Lefty Frizzell or Buck Owens, and I live in Austin so it’s mandatory to be into Willie Nelson, Robert Earl Keen, Hays Carll, and the late, great Billy Joe Shaver (maybe my favorite musician of all time). Mainstream country though? That’s a totally different animal. I like *some* newer country artists like Maren Morris or Kacey Musgraves (i.e. not really typical country at all), but I was only really into mainstream country during its 90’s boom when I was in or right out of college.
Just for fun and out of curiosity, I’ve been on a couple year mission to reevaluate what out of the country music of that that time is still worth listening to and what was “Mighty Morphin Power Rangers” level cheesy. I’m going hard into Comic Book Guy mode here as I make up some categories and lists:
Albums in rotation for me
I have plenty of playlists with 90’s country, but what albums are still worthwhile to me for a full play? I only came up with a few:
This Time by Dwight Yoakum. Such an awesome, timeless album. It’s the “Sharon Stone dumped me and I’m sad” album.
What a Crying Shame by the Mavericks. One of my all time favorite bands, and I even proposed to my wife right before seeing a Raul Malo concert at Gruene Hall. Trampoline from ’98 is my overall favorite Mavericks album but I’m saying they’d moved long past country by that point anyway.
Killin’ Time by Clint Black. Never got into anything else he ever did though
Weirdly enough, I still like Thinkin’ Problem by David Ball. Still holds up for me even though it wasn’t any kind of big hit
Born to Fly by Sara Evans. That might have been the very last mainstream country CD I ever purchased. Think it was in the 2000’s, but still including it here!
What surprisingly holds up for me
I remember liking him at the time, but I will happily pull out Joe Diffie’s (RIP, and an early COVID casualty:( ) greatest hits and play that. The humor still works, and I think he had a genuinely great voice. I’ll see your John Deere Green, but give me Prop Me Up By the Jukebox When I Die.
Mark Chesnutt. I liked him at the time, remember him always being in the background on the radio, but I don’t think I appreciated how good he was until I tried out his Greatest Hits collection for this post. Same kind of humor (Bubba Shot the Jukebox) as Joe Diffie, but his ballads hold up for me too. Awesome voice too.
Sammy Kershaw. I’ll still pull out his greatest hits once in awhile. I loved him at the time of course
Um, what about the women?
This post has been a bit of a sausage fest so far, and that’s not fair. Let’s talk about the women too! I think just like today, that the female artists hold up better over all somehow.
Her biggest stuff came later, but I was Sara Evans fan from the start and I’ll still play her music sometimes
Joe Dee Messina was awfully good in the late 90’s, and Head’s Caroline, Tails California still merits cranking up the volume if it ever comes on the radio
Faith Hill was cheesy to me when she first got started, but I like her later, admittedly poppy stuff.
Shania Twain. I absolutely remember why 20-something guys like me were into her, a couple songs were fun, but man, that stuff is cheesy
I still like Suzy Bogguss
I don’t mind Trisha Yearwood or Mary Chapin Carpenter, but I think they were products of their time and they sound really dated to me now
The Chicks were and are the real thing. I liked their first couple albums, but Home from ’02 is their best in my opinion. Doesn’t hurt that there was a strong Austin influence on that album from the song writers
Doug Stone ballads walked right up to the line between “good” and “cheesy”.
What about Garth Brooks or George Strait?
Just like Alan Jackson, the two biggest country guys of the 90’s could easily swerve from “hey, I really like that” to “zomg, that’s so cheesy I can’t believe anybody would ever listen to that on purpose.”
For Garth Brooks, give me Friends in Low Places & Shameless, and keep The Dance or Unanswered Prayers on the sideline. For George Strait, I think I’d throw out most of his 90’s music, but keep his earlier stuff like Amarillo by Morning or Baby Blue as a guilty pleasure.
Not counting Americana, but if I was…
For the sake of this post, I’m only going to consider mainstream artists and not bands that I think of as primarily being Americana or Alt Country. That being said, I think the Alt Country music of that era absolutely holds up:
Every single album that Shaver put out in his 90’s renaissance was fantastic, but I’m calling out Unshaven: Live at Smith’s Olde Bar as one of my all time favorite albums of any era or genre. Check it out, but make sure you play it loud. I cannot overstate how good those guys were live in the 90’s before Eddie Shaver passed away.
Joe Ely put out some great albums in the 90’s too, with Letter to Laredo being my favorite of his
Robert Earl Keen was prolific, and I’ll toss up No. 2 Live Dinner as my favorite of that time, but I would accept A Bigger Piece of Sky, Gringo Honeymoon, or Walking Distance as very strong contenders. With Feelin’ Good Again being one of my favorite songs of all time
The Mavericks are one of my all time favorite bands, and they’ve long since transcended country, but they started as a very good traditional country band
Alan Jackson is across the whole spectrum between genuinely great stuff (Chasing that Neon Rainbow) and oh my gosh, that’s absurdly cheesy and I’m embarrassed for people that like that (Small Town Country Man)
Sawyer Brown, but some of their stuff is too maudlin
I have a soft spot for Sara Evans partially since she’s from Missouri, but also,
Faith Hill’s later music when she admittedly got a little poppier
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:
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
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
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.
If you’re not familiar with it, Lamar is an IoC/DI container for .NET. It was originally built to be a faster, modernized, ASP.Net Core-compliant replacement for the much older StructureMap IoC library and also as a necessary subsystem of what is now Wolverine.
First off, if you have a vested investment in continuing to use Lamar in your development environment, it’s going to be continued to be supported for the time being for any necessary bug fixes, performance issues, or the inevitable changes when Microsoft drops a new .NET release that breaks Lamar somehow. I don’t expect there to be any new feature development with Lamar other than that though.
I do want to start deprecating Lamar throughout the rest of the JasperFx / “Critter Stack” ecosystem though. First off, I think it’s increasingly untenable in the long run to maintain any custom IoC container tool in the .NET ecosystem as Microsoft continues to bake in new assumptions about capabilities and behavior directly into the .NET ServiceProvider (the new “keyed services” feature was done in a really weird way and I see that as a harbinger of yet more pain coming from our MS overlords). Second, we’ve had a few complaints about how Wolverine requires Lamar as its IoC container and silently replaces the built in container in your system. Not that many people really care, but the ones who do have been kind of nasty about it, so it’s just time for the Wolverine coupling to Lamar to go. Third — and I think this is going to be a very good thing in the long run for all of us — the community as a whole seems to be settling on mostly using the small subset of core IoC container behavior that’s exposed by the abstracted IServiceProvider interface and that’s largely making IoC tools a commodity.
You might say that the type scanning thing sounds like Scrutor, but I’d say that it’s the other way around as the StructureMap type scanning predates Scrutor by many years.
On the way out, I’d like to run through some of the “special” features of both StructureMap and Lamar as a kind of wake for two projects I spent way too much time on over the years.
Passing Arguments at Runtime with StructureMap
For the most part, when you use an IoC container today you’re just letting it do what we used to call “auto-wiring” to use its configuration to figure out exactly what the full build plan is for a service and all its dependencies. But, StructureMap also let you go loose-y goose-y and pass in dependencies at runtime:
var widget = new BWidget();
var service = new BService();
var guyWithWidgetAndService = container
.With<IWidget>(widget)
.With<IService>(service)
.GetInstance<GuyWithWidgetAndService>();
In this case, StructureMap would build GuyWithWidgetAndService with all of its normal build plan, but substitute in the IWidget and IService value passed into the fluent interface above.
This feature was very flexible, endlessly vulnerable to permutations, and caused StructureMap to be less performant because of all of its runtime logic. I purposely ditched this with Lamar so that Lamar could “bake” in its build plans and pre-compile functions to build objects at runtime (I think basically every mainstream IoC tool you’d likely use in .NET does it this way now).
Inline Dependencies
Both Lamar and StructureMap allowed you to create service registrations that specified “inline” dependencies for only that service registration:
// ServiceRegistry is Lamar's analogue to IServiceCollection
// The For<T>().Use<TConcrete>() syntax is Lamar's older version
// of the AddSingleton<T>() extension methods used today
// with IServiceCollection
public class InlineCtorArgs : ServiceRegistry
{
public InlineCtorArgs()
{
// Defining args by type
For<IEventRule>().Use<SimpleRule>()
.Ctor<ICondition>().Is<Condition1>()
.Ctor<IAction>().Is<Action1>()
.Named("One");
// Pass the explicit values for dependencies
For<IEventRule>().Use<SimpleRule>()
.Ctor<ICondition>().Is(new Condition2())
.Ctor<IAction>().Is(new Action2())
.Named("Two");
}
}
Not something I’ve used myself in the recent past, but at one point this was relatively common. I used this to build rules engines with the “Event Condition Action” pattern.
Built In Environment Checks
Several containers now have diagnostics that allow you to verify the configuration by basically saying “do I know enough to build everything that is registered and is any implied dependencies missing?”. Lamar & StructureMap both did that with their diagnostics (which are more robust than the built in DI container, thank you), but a special little hook they have is built in environment checks as shown below:
public class DatabaseUsingService
{
private readonly DatabaseSettings _settings;
public DatabaseUsingService(DatabaseSettings settings)
{
_settings = settings;
}
[ValidationMethod]
public void Validate()
{
// For *now*, Lamar requires validate methods be synchronous
using (var conn = new SqlConnection(_settings.ConnectionString))
{
// If this blows up, the environment check fails:)
conn.Open();
}
}
}
That allowed you to do some additional runtime checking to verify a system was ready to work when container.AssertConfigurationIsValid(); is called. Just to show my age, this feature was originally added to probe whether or not required COM components were registered locally during deployment so that a deployment could “fail fast” and be quickly reverted.
DisposalLock because devs just can’t be trusted!
I bumped into this one just today. Let’s say that you have some naughty code in your system that is unintentionally trying to dispose the root container for your application (don’t laugh, it happened enough to spawn this feature). StructureMap/Lamar have a feature specifically for this problem that allows you to “lock” the disposal of the container to either find or stop the problem:
var container = Container.Empty();
// Ignore any calls to Container.Dispose()
container.DisposalLock = DisposalLock.Ignore;
// Throw an exception right here and now so we
// can find out who is erroneously disposing the container!
container.DisposalLock = DisposalLock.ThrowOnDispose;
// Normal mode
container.DisposalLock = DisposalLock.Unlocked;
container.Dispose();
Decorators and Interceptors
Lamar has a strong model for registering decorators or interceptors on service registrations to carry out some kind of action on a just build object or to wrap the inner behavior with a decorator.
public class WidgetDecorator : IWidget
{
public WidgetDecorator(IThing thing, IWidget inner)
{
Inner = inner;
}
public IWidget Inner { get; }
public void DoSomething()
{
// do something before
Inner.DoSomething();
// do something after
}
}
var container = new Container(_ =>
{
// This usage adds the WidgetHolder as a decorator
// on all IWidget registrations
_.For<IWidget>().DecorateAllWith<WidgetDecorator>();
// The AWidget type will be decorated w/
// WidgetHolder when you resolve it from the container
_.For<IWidget>().Use<AWidget>();
_.For<IThing>().Use<Thing>();
});
// Snippet of code from a unit test in the Lamar
// codebase
container.GetInstance<IWidget>()
.ShouldBeOfType<WidgetDecorator>()
.Inner.ShouldBeOfType<AWidget>();
This isn’t a feature I’ve personally used in years, but it was very common at one point. It’s also an awesome way for a team to bake in some magic behavior off to the side where folks won’t be able to find it easily later when the code misbehaves. At one point, I was absolutely ready to throttle very early MediatR users who abused the hell out of Lamar decorators as a middleware strategy and constantly asked me for help before MediatR got a usable middleware strategy.
Child Containers in StructureMap
Child Containers in StructureMap (or Profiles) were partially isolated containers that inherited service registrations (and singletons) from a root container and allowed you to make selected overrides of the parent containers.
Let’s just start by looking at this sample code:
[Fact]
public void show_a_child_container_in_action()
{
var parent = new Container(_ =>
{
_.For<IWidget>().Use<AWidget>();
_.For<IService>().Use<AService>();
});
// Create a child container and override the
// IService registration
var child = parent.CreateChildContainer();
child.Configure(_ =>
{
_.For<IService>().Use<ChildSpecialService>();
});
// The child container has a specific registration
// for IService, so use that one
child.GetInstance<IService>()
.ShouldBeOfType<ChildSpecialService>();
// The child container does not have any
// override of IWidget, so it uses its parent's
// configuration to resolve IWidget
child.GetInstance<IWidget>()
.ShouldBeOfType<AWidget>();
}
Sigh, folks loved this feature way back in the day. Especially for bigger, heavy client applications and sometimes for multi-tenancy. For me as a maintainer, it was a never ending nightmare to support because of all the possible permutations and how users would interpret the “proper” behavior differently. It also made StructureMap slow and more complex. I jettisoned this with Lamar to both optimize performance and to simplify my life.
There’s a very important lesson out of this feature and others in this page, the flexibility of a software tool and the performance characteristics of a software tool are often in conflict and there’s a tradeoff to be made.
Setter Injection
StructureMap and Lamar both supported Setter Injection where instead of only supplying dependencies through constructor functions, Lamar can also set dependencies on properties. There were various ways to do that, but the simplest (and ugliest) was through attributes:
public class Repository
{
// Adding the SetterProperty to a setter directs
// Lamar to use this property when
// constructing a Repository instance
[SetterProperty] public IDataProvider Provider { get; set; }
[SetterProperty] public bool ShouldCache { get; set; }
}
But you could also do it through policies:
public class ClassWithNamedProperties
{
public int Age { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public IGateway Gateway { get; set; }
public IService Service { get; set; }
}
[Fact]
public void specify_setter_policy_and_construct_an_object()
{
var theService = new ColorService("red");
var container = new Container(x =>
{
x.For<IService>().Use(theService);
x.For<IGateway>().Use<DefaultGateway>();
x.ForConcreteType<ClassWithNamedProperties>().Configure.Setter<int>().Is(5);
x.Policies.SetAllProperties(
policy => policy.WithAnyTypeFromNamespace("StructureMap.Testing.Widget3"));
});
var description = container.Model.For<ClassWithNamedProperties>().Default.DescribeBuildPlan();
Debug.WriteLine(description);
var target = container.GetInstance<ClassWithNamedProperties>();
target.Service.ShouldBeSameAs(theService);
target.Gateway.ShouldBeOfType<DefaultGateway>();
}
Now, this obviously sets up users for potential confusion about what properties are or are not being fulfilled by Lamar. I’m much more tolerant of a little “magic” in my code tools and I put a higher priority on “cleaner” looking code than I do on insisting that all code be explicit (and too ugly to actually read), but those “magic” tools absolutely have to be backed up with some kind of diagnostic that can unravel the “magic” behavior. Lamar actually has this now with its ability to preview the “build plan” for exactly how it’s going to resolve and build a service registration:
var container = new Container(x =>
{
x.For<IEngine>().Use<Hemi>().Named("The Hemi");
x.For<IEngine>().Add<VEight>().Singleton().Named("V8");
x.For<IEngine>().Add<FourFiftyFour>();
x.For<IEngine>().Add<StraightSix>().Scoped();
x.For<IEngine>().Add(c => new Rotary()).Named("Rotary");
x.For<IEngine>().Add(c => c.GetService<PluginElectric>());
x.For<IEngine>().Add(new InlineFour());
x.For<IEngine>().UseIfNone<VTwelve>();
});
// Little heavy weight, but this would show the equivalent C#
// code that would demonstrate what Lamar is doing to build
// or resolve every single service registration it has
Console.WriteLine(container.HowDoIBuild());
I’ve always been in the camp that says that setter injection is not ideal and that constructor injection (or method injection in newer frameworks like Wolverine or Minimal API), but there was from time to time a valid reason to use setter injection. Usually when there was some kind of inheritance involved — but that’s its own set of problems too.
Lamar’s diagnostics are head and shoulders better than the built in DI container from Microsoft and I think this will ultimately be the thing I miss most when Lamar is truly retired.
Overriding Services at Runtime
Lamar has some rump ability to do this, but StructureMap gave us the ultimate in runtime flexibility to override service registrations at will at runtime:
[Fact]
public void change_default_in_an_existing_container()
{
var container = new Container(x => { x.For<IFoo>().Use<AFoo>(); });
container.GetInstance<IFoo>().ShouldBeOfType<AFoo>();
// Now, change the container configuration
container.Configure(x => x.For<IFoo>().Use<BFoo>());
// The default of IFoo is now different
container.GetInstance<IFoo>().ShouldBeOfType<BFoo>();
// or use the Inject method that's just syntactical
// sugar for replacing the default of one type at a time
container.Inject<IFoo>(new CFoo());
container.GetInstance<IFoo>().ShouldBeOfType<CFoo>();
}
This was awesome for integration testing scenarios that depended on the IoC container in tests, but it was an unholy nightmare for me to maintain because it’s a permutation hell kind of trap. Yet again, Lamar chose performance and simplicity over flexibility and dropped this feature.
This is probably the one thing that people complain about the most switching from StructureMap to Lamar. I sympathize, but stand by my decisions there.
Missing Registration Policies
A lot of StructureMap was built in the days when Ruby on Rails seemed to be poised to dominate the development landscape, and I thought that Ruby’s missing method functionality looked cool as hell. StructureMap & Lamar both have a capability to “discover” or determine missing dependencies at runtime. While this was extensible, and we did use it on projects here and there, it was used internally by Lamar to “auto close” open generic types, find registrations for IEnumerable<T> by looking for T registrations, and some others.
The one Lamar/StructureMap feature (besides the diagnostics) I miss when using ServiceProvider is that Lamar can auto-resolve a concrete type upon request as long as Lamar can find all the necessary dependencies.
Lamar also has several policy types that would enable you to alter how registrations are actually built based on user defined policies like:
Use the “invoice” database connection string anytime the concrete type is in the namespace “OutApplication.Invoices”
Again, lots of opportunity for confusing the hell out of folks, and you’d absolutely want the magic unraveling diagnostics Lamar has in order to use something like that.
Summary
I’ve given up on several long running OSS projects in the 10-12 years with varying emotions of anger, disappointment, relief, or even a feeling of accomplishment. With Lamar I feel like it served its purpose in its time, and anyway:
“The world has moved on,’ we say… we’ve always said. But it’s moving on faster now. Something has happened to time.”
The goal for the “Critter Stack” tools is to be the absolute best set of tools for building server side .NET applications, and especially for any usage of Event Driven Architecture approaches. To go even farther, I would like there to be a day where organizations purposely choose the .NET ecosystem just because of the benefits that the “Critter Stack” provides over other options. But for now, that’s the journey we’re on. This post demonstrates an important new feature that I think fills in a huge capability gap that has long bothered me.
And as always, JasperFx Software is happy to work with any “Critter Stack” users through either support contracts or consulting engagements to help you wring the most value out of our tools and help you succeed with what you’re building.
I recently wrote some posts about the whole “Modular Monolith” architecture approach:
This week I’m helping a JasperFx client who has some complicated multi-tenancy requirements. In one of their services they have some types of event streams that need to use “conjoined multi-tenancy“, but at least one type of event stream (and related aggregate) that is global across all tenants. Marten event stores are either multi-tenanted or they’re not, with no mixing and matching. It occurred to me that we could solve this issue by putting the one type of global event streams in a separate Marten store. Even though the 2nd Marten store will still target the exact same PostgreSQL database (but in a different schema), we can give this second schema a different configuration to accommodate the different tenancy rules. Moreover, this would even be a good way to improve performance and scalability of their service by effectively sharding the events and streams tables (smaller tables generally mean better performance).
At the same time, I’m also helping them introduce Wolverine message handlers as well, and I really wanted to be able to use the aggregate handler workflow for commands that spawn new Marten events (effectively the Critter Stack version of the “Decider” pattern, but lower ceremony). I finally took some time — and stumbled onto a workable approach — that finally adds far better support for modular monolith architectures with the Wolverine 2.13.0 release that hit today.
To see a sneak peek, let’s say that you have two additional Marten stores for your application like these two:
public interface IPlayerStore : IDocumentStore;
public interface IThingStore : IDocumentStore;
You can now bootstrap a Marten + Wolverine application (using the WolverineFx.Marten Nuget dependency) like so:
theHost = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
opts.Services.AddMarten(Servers.PostgresConnectionString).IntegrateWithWolverine();
opts.Policies.AutoApplyTransactions();
opts.Durability.Mode = DurabilityMode.Solo;
opts.Services.AddMartenStore<IPlayerStore>(m =>
{
m.Connection(Servers.PostgresConnectionString);
m.DatabaseSchemaName = "players";
})
// THIS AND BELOW IS WHAT IS NEW FOR WOLVERINE 2.13
.IntegrateWithWolverine()
// Add a subscription
.SubscribeToEvents(new ColorsSubscription())
// Forward events to wolverine handlers
.PublishEventsToWolverine("PlayerEvents", x =>
{
x.PublishEvent<ColorsUpdated>();
});
// Look at that, it even works with Marten multi-tenancy through separate databases!
opts.Services.AddMartenStore<IThingStore>(m =>
{
m.MultiTenantedDatabases(tenancy =>
{
tenancy.AddSingleTenantDatabase(tenant1ConnectionString, "tenant1");
tenancy.AddSingleTenantDatabase(tenant2ConnectionString, "tenant2");
tenancy.AddSingleTenantDatabase(tenant3ConnectionString, "tenant3");
});
m.DatabaseSchemaName = "things";
}).IntegrateWithWolverine(masterDatabaseConnectionString:Servers.PostgresConnectionString);
opts.Services.AddResourceSetupOnStartup();
}).StartAsync();
Now, moving to message handlers or HTTP endpoints, you will have to explicitly tag either the containing class or individual messages with the [MartenStore(store type)] attribute like this simple example below:
// This will use a Marten session from the
// IPlayerStore rather than the main IDocumentStore
[MartenStore(typeof(IPlayerStore))]
public static class PlayerMessageHandler
{
// Using a Marten side effect just like normal
public static IMartenOp Handle(PlayerMessage message)
{
return MartenOps.Store(new Player{Id = message.Id});
}
}
Boom! Even that minor sample is using transactional middleware targeting Marten and able to work with the separate IPlayerStore. This new integration includes:
Transactional outbox support for all configured Marten stores
Transactional middleware
The “aggregate handler workflow”
Marten side effects
Subscriptions to Marten events
Multi-tenancy, both “conjoined” Marten multi-tenancy and multi-tenancy through separate databases
I’m maybe a little too excited for a feature that most users will never touch, but for those who do see this, the “Critter Stack” now has first class modular monolith support across a wide range of the features that make the “Critter Stack” a desirable platform in the first place.
If you really need to have strong typed identifier support in Marten right now, here’s the long standing workaround.
Some kind of support for “strong typed identifiers” has long been a feature request for Marten from our community. I’ve even been told by a few folks that they wouldn’t consider using Marten until it did have this support. I’ve admittedly been resistant to adding this feature strictly out of (a very well founded) fear that tackling that would be a massive time sink that didn’t really improve the tool in any great way (I’m hoping to be wrong about that).
My reticence about this aside, it came up a couple times in the past week from JasperFx Software customers, and that magically ratchets up the priority quite a bit. That all being said, here’s a little preview of some ongoing work for the next Marten feature release.
Let’s say that you’re using the Vogen library for value types and want to use this custom type for the identity of an Invoice document in Marten:
[ValueObject<Guid>]
public partial struct InvoiceId;
public class Invoice
{
// Marten will use this for the identifier
// of the Invoice document
public InvoiceId? Id { get; set; }
public string Name { get; set; }
}
Jumping to some already passing tests, Marten can assign an identity to a new document is one is missing just like it would today for Guid identities:
[Fact]
public void store_document_will_assign_the_identity()
{
var invoice = new Invoice();
theSession.Store(invoice);
// Marten sees that there is no existing identity,
// so it assigns a new identity
invoice.Id.ShouldNotBeNull();
invoice.Id.Value.Value.ShouldNotBe(Guid.Empty);
}
Because this actually does matter for database performance, Marten is using a sequential Guid inside of the custom InvoiceId type. Following Marten’s desire for a “it just works” development experience, Marten is able to “know” how to work with the InvoiceId type generated by Vogen without having to require any kind of explicit mapping or mandatory interfaces on the identity type — which I thought was pretty important to keep your domain code from being coupled to Marten.
Moving to basic use cases, here’s a passing test for storing and loading a new document from the database:
[Fact]
public async Task load_document()
{
var invoice = new Invoice{Name = Guid.NewGuid().ToString()};
theSession.Store(invoice);
await theSession.SaveChangesAsync();
(await theSession.LoadAsync<Invoice>(invoice.Id))
.Name.ShouldBe(invoice.Name);
}
and a look at how the strong typed identifiers can play in LINQ expressions so far:
[Fact]
public async Task use_in_LINQ_where_clause()
{
var invoice = new Invoice{Name = Guid.NewGuid().ToString()};
theSession.Store(invoice);
await theSession.SaveChangesAsync();
var loaded = await theSession.Query<Invoice>().FirstOrDefaultAsync(x => x.Id == invoice.Id);
loaded
.Name.ShouldBe(invoice.Name);
}
[Fact]
public async Task load_many()
{
var invoice1 = new Invoice{Name = Guid.NewGuid().ToString()};
var invoice2 = new Invoice{Name = Guid.NewGuid().ToString()};
var invoice3 = new Invoice{Name = Guid.NewGuid().ToString()};
theSession.Store(invoice1, invoice2, invoice3);
await theSession.SaveChangesAsync();
var results = await theSession
.Query<Invoice>()
.Where(x => x.Id.IsOneOf(invoice1.Id, invoice2.Id, invoice3.Id))
.ToListAsync();
results.Count.ShouldBe(3);
}
[Fact]
public async Task use_in_LINQ_order_clause()
{
var invoice = new Invoice{Name = Guid.NewGuid().ToString()};
theSession.Store(invoice);
await theSession.SaveChangesAsync();
var loaded = await theSession.Query<Invoice>().OrderBy(x => x.Id).Take(3).ToListAsync();
}
There’s a world of use case permutations yet to go (bulk writing, numeric identities with HiLo generation, Include() queries, more LINQ scenarios, magically adding JSON serialization converters, using StrongTypedId as well), but I think we’ve got a solid start on a long asked for feature that I’ve previously been leery of building out.
In the previous post we learned how to keep all the document or event data for each tenant in the same database, but using Marten’s “conjoined multi-tenancy” model to keep the data separated. This time out, let’s go for a much higher degree of separation by using a completely different database for each tenant with Marten.
Marten has a couple different recipes for “database per tenant multi-tenancy”, but let’s start with the simplest possible model where we’ll explicitly tell Marten about every single tenant by its id (the tenant_id values) and a connection string to that tenant’s specific database:
var builder = Host.CreateApplicationBuilder();
var configuration = builder.Configuration;
builder.Services.AddMarten(opts =>
{
// Setting up Marten to "know" about five different tenants
// and the database connection string for each
opts.MultiTenantedDatabases(tenancy =>
{
tenancy.AddSingleTenantDatabase(configuration.GetConnectionString("tenant1"), "tenant1");
tenancy.AddSingleTenantDatabase(configuration.GetConnectionString("tenant2"), "tenant2");
tenancy.AddSingleTenantDatabase(configuration.GetConnectionString("tenant3"), "tenant3");
tenancy.AddSingleTenantDatabase(configuration.GetConnectionString("tenant4"), "tenant4");
tenancy.AddSingleTenantDatabase(configuration.GetConnectionString("tenant5"), "tenant5");
});
});
using var host = builder.Build();
await host.StartAsync();
Just like in the post on conjoined tenancy, you can open a Marten document session (Marten’s unit of work abstraction for most typical operations) by supplying the tenant id like so:
// This was a recent convenience method added to
// Marten to fetch the IDocumentStore singleton
var store = host.DocumentStore();
// Open up a Marten session to the database for "tenant1"
await using var session = store.LightweightSession("tenant1");
With that session object above, you can query all the data in that one specific tenant, or write Marten documents or events to that tenant database — and only that tenant database.
Now, to answer some questions you might have:
Marten’s DocumentStore is still a singleton registered service in your application’s IoC container that “knows” about multiple databases that are assumed to be identical. DocumentStore is an expensive object to create, and an important part of Marten’s multi-tenancy strategy was to ensure that you only every needed one — even with multiple tenant databases
Marten is able to track the schema object creation completely separate for each tenant database, so the “it just works” default mode where Marten is completely able to do database migrations for you on the fly also “just works” with the multi-tenancy by separate database approach
Marten’s (really Weasel‘s) command line tooling is absolutely able to handle multiple tenant databases. You can either migrate or patch all databases, or one database at a time through the command line tools
Marten’s Async Daemon background processing of event projections is perfectly capable of managing the execution against multiple databases as well
We’ll get into this in a later post, but it’s also possible to do two layers of multi-tenancy by combining both separate databases and conjoined multi-tenancy
Moving to a bit more complex case, let’s use Marten’s relatively recent “master table tenancy” model that will locate a table of tenant identifiers to tenant database connection strings in a table in a “master” database:
var builder = Host.CreateApplicationBuilder();
var configuration = builder.Configuration;
builder.Services.AddMarten(opts =>
{
var tenantDatabaseConnectionString = configuration.GetConnectionString("tenants");
opts.MultiTenantedDatabasesWithMasterDatabaseTable(tenantDatabaseConnectionString);
});
using var host = builder.Build();
await host.StartAsync();
The usage at runtime is identical to any other kind of multi-tenancy in Marten, but this model gives you the ability to add new tenants and tenant database at runtime without any down time. Marten will still be able to recognize a new tenant id and apply any necessary database changes at runtime.
Summary and What’s Next
Using separate databases for each tenant is a great way to create an even more rigid separation of data. You might opt for this model as a way to:
Scale your system better by effectively sharding your customer databases into smaller databases
Potentially reduce hosting costs by placing high volume tenants on different hardware than lower volume tenants
Meet more rigid security requirements for less risk of tenant data being exposed incorrectly
To the last point, I’ve heard of several cases where regulatory concerns have trumped technical concerns and led teams to choose the tenant per database approach.
Of course, the obvious potential downsides are more complex deployments, more things to go wrong, and maybe higher hosting costs if you’re not careful. Yeah, I know I said that’s a potential cost savings, that sword can cut both ways, so just be aware of potential hosting cost changes.
As for what’s next, actually quite a bit! In subsequent posts we’ll dig into Wolverine’s multi-tenancy support, detecting the tenant id from HTTP requests, two level tenancy in Marten because’s that’s possible, and even Wolverine’s ability to spawn virtual actors by tenant id.
For my fellow Gen X’ers out there who keep hearing the words “keep the data separated” and naturally have this song stuck in your head: