"Separate" or Ancillary Stores 2.13
Let's say that you want to use the full "Critter Stack" inside of a modular monolith architecture. With Marten, you might well want to use its "Separate Store" feature ("ancillary" in Wolverine parlance) to split up the modules so they are accessing different, logical databases -- even if in the end everything is stored in the exact same PostgreSQL database. However, even with separate Marten document stores, you still want Wolverine's:
- Transaction middleware support, including the transactional outbox
- Scheduled message support -- which is really part of the outbox anyway
- Subscriptions to Marten events captured by these separate stores
- Marten side effect model (
MartenOps) - Ability to automatically set up the necessary envelope storage tables and functions in each database or separate schema
Well now you can get that, but there's a few explicit steps to take.
First off, you need to explicitly and individually tag each Marten store that you want to be integrated with Wolverine in your bootstrapping.
From the Wolverine tests, say you have these two separate stores:
public interface IPlayerStore : IDocumentStore;
public interface IThingStore : IDocumentStore;We can add Wolverine integration to both through a similar call to IntegrateWithWolverine() as normal as shown below:
theHost = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
// THIS IS IMPORTANT FOR MODULAR MONOLITH USAGE!
// This helps Wolverine out to always utilize the same envelope storage
// for all modules for more efficient usage of resources
opts.Durability.MessageStorageSchemaName = "wolverine";
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";
})
.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(x => x.MainConnectionString = Servers.PostgresConnectionString);
opts.Services.AddResourceSetupOnStartup();
}).StartAsync();Let's specifically zoom in on this code from within the big sample above:
// THIS IS IMPORTANT FOR MODULAR MONOLITH USAGE!
// This helps Wolverine out to always utilize the same envelope storage
// for all modules for more efficient usage of resources
opts.Durability.MessageStorageSchemaName = "wolverine";If you are using separate Marten document stores for different modules in your application, you can easily make Wolverine happily share the transactional inbox/outbox between modules (you do want to do this to save on resource usage) by ensuring that all the document stores have the same database schema for envelope storage. The opts.Durability.MessageStorageSchemaName value can be used to help Wolverine out to share the transactional inbox/outbox storage across all Marten stores that target the same physical database.
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 });
}
}INFO
At this point the "Critter Stack" team is voting to make the attribute an explicit requirement rather than trying any kind of conventional application of what handlers/messages/HTTP routes are covered by what Marten document store
So what's possible so far?
- The transactional inbox support is available in 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
- Wolverine managed projection or subscription distribution
- The "Event Forwarding" from Marten to Wolverine, but that is either 100% enabled for all Marten stores through the main Marten store registration or not at all
TIP
In the case of the ancillary Marten stores, the IDocumentSession objects are "lightweight" sessions without any identity map mechanics for better performance.
What's not (yet) supported
- It is not possible to use more than one ancillary store in the same handler with the middleware
- Fine grained configuration of the
IDocumentSessionobjects created for the ancillary stores, so no ability to tag customIDocumentSessionListenerobjects or control the session type. Listeners could be added through Wolverine middlware though - The PostgreSQL messaging transport will not span the ancillary databases, but will still work if the ancillary store is targeting the same database

