I just pulled the trigger on Marten 8.8 and Wolverine 4.10 earlier today. Neither is particularly large, but there’s some new toys and an important improvement for test automation support that are worth calling out.
My goodness, that title is a mouthful. I’ve been helping a couple different JasperFx Software clients and community users on Discord with their test automation harnesses. In all cases, there was some complexity involved because of the usage of some mix of asynchronous projections or event subscriptions in Marten or asynchronous messaging with Wolverine. As part of that work to support a client today, Marten has this new trick (with a cameo from the related JasperFx Alba tool for HTTP service testing):
// This is bootstrapping the actual application using
// its implied Program.Main() set up
Host = await AlbaHost.For<Program>(b =>
{
b.ConfigureServices((context, services) =>
{
// Important! You can make your test harness work a little faster (important on its own)
// and probably be more reliable by overriding your Marten configuration to run all
// async daemons in "Solo" mode so they spin up faster and there's no issues from
// PostgreSQL having trouble with advisory locks when projections are rapidly started and stopped
// This was added in V8.8
services.MartenDaemonModeIsSolo();
services.Configure<MartenSettings>(s =>
{
s.SchemaName = SchemaName;
});
});
});
Specifically note the new `IServiceCollection.MartenDaemonModeIsSolo()`. That is overriding any Marten async daemons that normally run with the “Hot/Cold” load distribution that is appropriate for production with Marten’s “Solo” load distribution so that your test harness can spin up much faster. In addition, this mode will enable Marten to more quickly shut down, then restart all asynchronous projections or subscriptions in tests when you use this existing testing helper to reset state:
// OR if you use the async daemon in your tests, use this
// instead to do the above, but also cleanly stop all projections,
// reset the data, then start all async projections and subscriptions up again
await Host.ResetAllMartenDataAsync();
In the above usage, that ResetAllMartenDataAsync() is smart enough to first disable all asynchronous projections and subscriptions, reset the Marten data store to your configured baseline state (effectively by wiping out all data, then reapplying all your “initial data”), then restarting all asynchronous projections and subscriptions from the new baseline.
Having the “Solo” load distribution will make the constant teardown and restart of the asynchronous projections faster than it would with a “Hot/Cold” configuration where Marten still assumes there might be other nodes running.
If you or your shop would want some assistance with test automation using the Critter Stack or otherwise, drop me a note at jeremy@jasperfx.net and I can chat about what we could do to help you out.
I’ll be discussing this new feature and quite a bit more in a live stream tomorrow (August 20th) at 2:00PM US Central time:
Let’s just say that Marten incurs some serious benefits to being on top of PostgreSQL and its very strong support for transactional integrity as opposed to some of the high profile commercial Event Sourcing tools who are spending a lot of time and energy on their “Dynamic Consistency Boundary” concept because they lack the ACID compliant transactions that Marten gets for free by riding on top of PostgreSQL.
Marten has long had the ability to support both reading and appending to multiple event streams at one time with guarantees about data consistency and even the ability to achieve strongly consistent transactional writes across multiple streams at one time. Wolverine just added some syntactic sugar to make cross-stream command handlers be more declarative with its “aggregate handler workflow” integration with Marten.
Using the canonical example of a use case where you move money from one account to another account and need both changes to be persisted in one atomic transaction. Let’s start with a simplified domain model of events and a “self-aggregating” Account type like this:
public record AccountCreated(double InitialAmount);
public record Debited(double Amount);
public record Withdrawn(double Amount);
public class Account
{
public Guid Id { get; set; }
public double Amount { get; set; }
public static Account Create(IEvent<AccountCreated> e)
=> new Account { Id = e.StreamId, Amount = e.Data.InitialAmount};
public void Apply(Debited e) => Amount += e.Amount;
public void Apply(Withdrawn e) => Amount -= e.Amount;
}
Moving on, here’s what a command handler could be that handles a TransferMoney command that impacts two different accounts:
public record TransferMoney(Guid FromId, Guid ToId, double Amount);
public static class TransferMoneyEndpoint
{
[WolverinePost("/accounts/transfer")]
public static void Post(
TransferMoney command,
[Aggregate(nameof(TransferMoney.FromId))] IEventStream<Account> fromAccount,
[Aggregate(nameof(TransferMoney.ToId))] IEventStream<Account> toAccount)
{
// Would already 404 if either referenced account does not exist
if (fromAccount.Aggregate.Amount >= command.Amount)
{
fromAccount.AppendOne(new Withdrawn(command.Amount));
toAccount.AppendOne(new Debited(command.Amount));
}
}
}
The IEventStream<T> abstraction comes from Marten’s FetchForWriting() API that is our recommended way to interact with Marten streams in typical command handlers. This API is used underneath Wolverine’s “aggregate handler workflow”, but normally hidden from user written code if you’re only working with one stream at a time. In this case though, we’ll need to work with the raw IEventStream<T> objects that both wrap the projected aggregation of each Account as well as providing a point where we can explicitly append events separately to each event stream. FetchForWriting() guarantees that you get the most up to date information for the Account view of each event stream regardless of how you have configured Marten’s ProjectionLifecycle for Account (kind of an important detail here!).
The typical Marten transactional middleware within Wolverine is calling SaveChangesAsync() for us on the Marten unit of work IDocumentSession for the command. If there’s enough funds in the “From” account, this command will append a Withdrawn event to the “From” account and a Debited event to the “To” account. If either account has been written to between fetching the original information, Marten will reject the changes and throw its ConcurrencyException as an optimistic concurrency check.
In unit testing, we could write a unit test for the “happy path” where you have enough funds to cover the transfer like this:
public class when_transfering_money
{
[Fact]
public void happy_path_have_enough_funds()
{
// StubEventStream<T> is a type that was recently added to Marten
// specifically to facilitate testing logic like this
var fromAccount = new StubEventStream<Account>(new Account { Amount = 1000 }){Id = Guid.NewGuid()};
var toAccount = new StubEventStream<Account>(new Account { Amount = 100}){Id = Guid.NewGuid()});
TransferMoneyEndpoint.Post(new TransferMoney(fromAccount.Id, toAccount.Id, 100), fromAccount, toAccount);
// Now check the events we expected to be appended
fromAccount.Events.Single().ShouldBeOfType<Withdrawn>().Amount.ShouldBe(100);
toAccount.Events.Single().ShouldBeOfType<Debited>().Amount.ShouldBe(100);
}
}
Alright, so there’s a few remaining items we still need to improve over time:
Today there’s no way to pass in the expected starting version of each individual stream
There’s some ongoing work to allow Wolverine to intelligently parallelize work between business entities or event streams while doing work sequentially within a business entity or event stream to side step concurrency problems
We’re working toward making Wolverine utilize Marten’s batch querying support any time you use Wolverine’s declarative persistence helpers against Marten and request more than one item from Marten. You can use Marten’s batch querying with its FetchForWriting() API today if you just drop down to the lower level and work directly against Marten, but wouldn’t it be nice if Wolverine would just do that automatically for you in cases like the TransferMoney command handler above? We think this will be a significant performance improvement because network round trips are evil.
I covered this example at the end of a live stream we did last week on Event Sourcing with the Critter Stack:
To continue a consistent theme about how Wolverine is becoming the antidote to high ceremony Clean/Onion Architecture approaches, Wolverine 4.8 added some significant improvements to its declarative persistence support (partially after seeing how a recent JasperFx Software client was encountering a little bit of repetitive code).
A pattern I try to encourage — and many Wolverine users do like — is to make the main method of a message handler or an HTTP endpoint be the “happy path” after validation and even data lookups so that that method can be a pure method that’s mostly concerned with business or workflow logic. Wolverine can do this for you through its “compound handler” support that gets you to a low ceremony flavor of Railway Programming.
With all that out of the way, I saw a client frequently writing code something like this endpoint that would need to process a command that referenced one or more entities or event streams in their system:
public record ApproveIncident(Guid Id);
public class ApproveIncidentEndpoint
{
// Try to load the referenced incident
public static async Task<(Incident, ProblemDetails)> LoadAsync(
// Say this is the request body, which we can *also* use in
// LoadAsync()
ApproveIncident command,
// Pulling in Marten
IDocumentSession session,
CancellationToken cancellationToken)
{
var incident = await session.LoadAsync<Incident>(command.Id, cancellationToken);
if (incident == null)
{
return (null, new ProblemDetails { Detail = $"Incident {command.Id} cannot be found", Status = 400 });
}
return (incident, WolverineContinue.NoProblems);
}
[WolverinePost("/api/incidents/approve")]
public SomeResponse Post(ApproveIncident command, Incident incident)
{
// actually do stuff knowing that the Incident is valid
}
}
I’d ask you to mostly pay attention to the LoadAsync() method, and imagine copy & pasting dozens of times in a system. And sure, you could go back to returning IResult as a continuation from the HTTP endpoint method above, but that moves clutter back into your HTTP method and would add more manual work to mark up the method with attributes for OpenAPI metadata. Or we could improve the OpenAPI metadata generation by returning something like Task<Results<Ok<SomeResponse>, ProblemHttpResult>>, but c’mon, that’s an absolute eye sore that detracts from the readability of the code.
Instead, let’s use the newly enhanced version of Wolverine’s [Entity] attribute to simplify the code above and still get OpenAPI metadata generation that reflects both the 200 SomeResponse happy path and 400 ProblemDetails with the correct content type. That would look like this:
[WolverinePost("/api/incidents/approve")]
public static SomeResponse Post(
// The request body. Wolverine doesn't require [FromBody], but it wouldn't hurt
ApproveIncident command,
[Entity(OnMissing = OnMissing.ProblemDetailsWith400, MissingMessage = "Incident {0} cannot be found")]
Incident incident)
{
// actually do stuff knowing that the Incident is valid
return new SomeResponse();
}
Behaviorally, at runtime that endpoint will try to load the Incident entity from whatever persistence tooling is configured for the application (Marten in the tests) using the “Id” property of the ApproveIncident object deserialized from the HTTP request body. If the data cannot be found, the HTTP requests ends with a 400 status code and a ProblemDetails response with the configured message up above. If the Incident can be found, it’s happily passed along to the main endpoint.
Not that every endpoint or message handler is really this simple, but plenty of times you would be changing a property on the incident and persisting it. We can *still* be mostly a pure function with the existing persistence helpers in Wolverine like so:
[WolverinePost("/api/incidents/approve")]
public static (SomeResponse, IStorageAction<Incident>) Post(
// The request body. Wolverine doesn't require [FromBody], but it wouldn't hurt
ApproveIncident command,
[Entity(OnMissing = OnMissing.ProblemDetailsWith400, MissingMessage = "Incident {0} cannot be found")]
Incident incident)
{
incident.Approved = true;
// actually do stuff knowing that the Incident is valid
return (new SomeResponse(), Storage.Update(incident));
}
Here’s some things I’d like you to know about that [Entity] attribute up above and how that is going to work out in real usage:
There is some default conventional magic going on to “decide” how to get the identity value for the entity being loaded (“IncidentId” or “Id” on the command type or request body type, then the same value in routing values for HTTP endpoints or declared query string values). This can be explicitly configured on the attribute something like [Entity(nameof(ApproveIncident.Id)]
Every attribute type that I’m mentioning in this post that can be applied to method parameters supports the same identity logic as I explained in the previous bullet
Before Wolverine 4.8, the “on missing” behavior was to simply set a 404 status code in HTTP or log that required data was missing in message handlers and quit. Wolverine 4.8 adds the ability to control the “on missing” behavior
This new “on missing” behavior is available on the older [Document] attribute in Wolverine.Http.Marten, and [Document] is now a direct subclass of [Entity] that can be used with either message handlers or HTTP endpoints now
The existing [AggregateHandler] and [Aggregate] attributes that are part of the Wolverine + Marten “aggregate handler workflow” (the “C” in CQRS) now support this “on missing” behavior, but it’s “opt in,” meaning that you would have to use [Aggregate(Required = true)] to get the gating logic. We had to make that required test opt in to avoid breaking existing behavior when folks upgraded.
The lighter weight [ReadAggregate] in the Marten integration also standardizes on this “OnMissing” behavior
Because of the confusion I was seeing from some users between [Aggregate]which is meant for writing events and is a little heavier runtime wise than [ReadAggregate], there’s a new [WriteAggregate] attribute with identical behavior to [Aggregate] and now available for message handlers as well. I think that [Aggregate] might get deprecated soon-ish to sidestep the potential confusion
[Entity] attribute usage is 100% supported for EF Core and RavenDb as well as Marten. Wolverine is even smart enough to select the correct DbContext type for the declared entity
If you coded with any of that [Entity] or Storage stuff and switched persistence tooling, your code should not have to change at all
There’s no runtime Reflection going on here. The usage of [Entity] is impacting Wolverine’s code generation around your message handler or HTTP endpoint methods.
The options so far for “OnMissing” behavior is this:
public enum OnMissing
{
/// <summary>
/// Default behavior. In a message handler, the execution will just stop after logging that the data was missing. In an HTTP
/// endpoint the request will stop w/ an empty body and 404 status code
/// </summary>
Simple404,
/// <summary>
/// In a message handler, the execution will log that the required data is missing and stop execution. In an HTTP
/// endpoint the request will stop w/ a 400 response and a ProblemDetails body describing the missing data
/// </summary>
ProblemDetailsWith400,
/// <summary>
/// In a message handler, the execution will log that the required data is missing and stop execution. In an HTTP
/// endpoint the request will stop w/ a 404 status code response and a ProblemDetails body describing the missing data
/// </summary>
ProblemDetailsWith404,
/// <summary>
/// Throws a RequiredDataMissingException using the MissingMessage
/// </summary>
ThrowException
}
The Future
This new improvement to the declarative data access is meant to be part of a bigger effort to address some bigger use cases. Not every command or query is going to involve just one single entity lookup or one single Marten event stream, so what do you do when there are multiple declarations for data lookups?
I’m not sure what everyone else’s experience is, but a leading cause of performance problems in the systems I’ve helped with over the past decade has been too much chattiness between the application servers and the database. The next step with the declarative data access is to have at least the Marten integration opt into using Marten’s batch querying mechanism to improve performance by batching up requests in fewer network round trips any time there are multiple data lookups in a single HTTP endpoint or message handler.
The step after that is to also enroll our Marten integration for command handlers so that you can craft message handlers or HTTP endpoints that work against 2 or more event streams with strong consistency and transactional support while also leveraging the Marten batch querying for all the efficiency we can wring out of the tooling. I mostly want to see this behavior because I’ve seen clients who could actually use what I was just describing as a way to make their systems more efficient and remove some repetitive code.
I’ll also admit that I think this capability to have an alternative “aggregate handler workflow” that allows you to work efficiently with more than one event stream and/or projected aggregate at one time would put the Critter Stack ahead of the commercial tools pursuing “Dynamic Consistency Boundaries” with what I’ll be arguing is an easier to use alternative.
It’s already possible to work transactionally with multiple event streams at one time with strong consistency and both optimistic and exclusive version protections, but there’s opportunity for performance optimization here.
Summary
Pride goeth before destruction, and an haughty spirit before a fall.
Proverbs 16:18 in the King James version
With the quote above out of the way, let’s jump into some cocky salesmanship! My hope and vision for the Critter Stack is that it becomes the most effective tooling for building typical server side software systems. My personal vision and philosophy for making software development more productive and effective over time is to ruthlessly reduce repetitive code and eliminate code ceremony wherever possible. Our community’s take is that we can achieve improved results compared to more typical Clean/Onion/Hexagonal Architecture codebases by compressing and compacting code down without ever sacrificing performance, resiliency, or testability.
The declarative persistence helpers in this article are, I believe, a nice example of the evolving “Critter Stack Way.”
This work was done at the behest of a JasperFx Software client. They only needed to vary header values between events, but while the hood was popped up on event metadata, we finally addressed the long awaited ability to explicitly set event timestamps.
First, we finally have the ability to allow users to modify metadata on an event by event basis including the event timestamp. This has been a long standing request from many folks to either facilitate testing scenarios or to enable easier data importing from other databases or event stores. And especially now that Marten is arguably the best event sourcing solution for .NET, folks really should have a viable path to import data from external sources.
You can do that either by grabbing the IEvent wrapper and modifying the timestamp, causation, correlation, event id (valuable for tracing event data back to external systems), or headers like this sample:
public static async Task override_metadata(IDocumentSession session)
{
var started = new QuestStarted { Name = "Find the Orb" };
var joined = new MembersJoined
{
Day = 2, Location = "Faldor's Farm", Members = new string[] { "Garion", "Polgara", "Belgarath" }
};
var slayed1 = new MonsterSlayed { Name = "Troll" };
var slayed2 = new MonsterSlayed { Name = "Dragon" };
var joined2 = new MembersJoined { Day = 5, Location = "Sendaria", Members = new string[] { "Silk", "Barak" } };
var action = session.Events
.StartStream<QuestParty>(started, joined, slayed1, slayed2, joined2);
// I'm grabbing the IEvent wrapper for the first event in the action
var wrapper = action.Events[0];
wrapper.Timestamp = DateTimeOffset.UtcNow.Subtract(1.Hours());
wrapper.SetHeader("category", "important");
wrapper.Id = Guid.NewGuid(); // Just showing that you *can* override this value
wrapper.CausationId = wrapper.CorrelationId = Activity.Current?.Id;
await session.SaveChangesAsync();
}
Or by appending an already wrapped IEvent as I’m showing here, along with some new convenience wrapper extension methods to make the mechanics a little more declarative:
public static async Task override_metadata2(IDocumentSession session)
{
var started = new QuestStarted { Name = "Find the Orb" };
var joined = new MembersJoined
{
Day = 2, Location = "Faldor's Farm", Members = new string[] { "Garion", "Polgara", "Belgarath" }
};
var slayed1 = new MonsterSlayed { Name = "Troll" };
var slayed2 = new MonsterSlayed { Name = "Dragon" };
var joined2 = new MembersJoined { Day = 5, Location = "Sendaria", Members = new string[] { "Silk", "Barak" } };
// The result of this is an IEvent wrapper around the
// started data with an overridden timestamp
// and a value for the "color" header
var wrapper = started.AsEvent()
.AtTimestamp(DateTimeOffset.UtcNow.Subtract(1.Hours()))
.WithHeader("color", "blue");
session.Events
.StartStream<QuestParty>(wrapper, joined, slayed1, slayed2, joined2);
await session.SaveChangesAsync();
}
The second approach is going to be necessary if you are appending events with the FetchForWriting() API (and you should be within any kind of CQRS “write” handler).
There is of course a catch. If you use the “QuickAppend” option in Marten and want to be able to override the event timestamps, you’ll need this slightly different option instead:
var builder = Host.CreateApplicationBuilder();
builder.Services.AddMarten(opts =>
{
opts.Connection(builder.Configuration.GetConnectionString("marten"));
// This is important!
opts.Events.AppendMode = EventAppendMode.QuickWithServerTimestamps;
});
To avoid causing database breaking changes when upgrading, the ability to override timestamps with the “QuickAppend” option required this new “opt in” setting because this forces Marten to generate both “glue” code and a database function a little differently.
Capturing the User Name on Persisted Events
These kinds of features have to be “opt in” so that we don’t cause database changes in a minor release when people upgrade. Having to worry about “opt in” or “opt out” mechanics and backwards compatibility is both the curse and enabler of long running software tool projects like Marten.
Another request from the back log was to have a first class tracking of the user name (or process name) in events based on the current user of whatever operation appended the events. Following along with the “opt in” support for tracking correlation and causation ids, we’ll first need to opt into storing the user name with events:
And now we can apply the user name to persisted events something like this:
public static async Task Handle(StartInvoice command, IDocumentSession session, ClaimsPrincipal principal)
{
// Marking the session as being modified by this active user
session.LastModifiedBy = principal.Identity.Name;
// Any events persisted by this session will be tagged with the current user
// in the database
session.Events.StartStream(new InvoiceStarted(command.Name, command.Amount));
await session.SaveChangesAsync();
}
And while this should probably only be used for diagnostics mostly, you can now query against the raw event data with LINQ for the user name (assuming that it’s captured of course!) like this sample from our tests:
[Theory]
[InlineData(JasperFx.Events.EventAppendMode.Rich)]
[InlineData(JasperFx.Events.EventAppendMode.Quick)]
public async Task capture_user_name_information(EventAppendMode mode)
{
EventAppendMode = mode;
var streamId = Guid.NewGuid();
theSession.LastModifiedBy = "Larry Bird";
// Just need a time that will be easy to assert on that is in the past
var timestamp = (DateTimeOffset)DateTime.Today.Subtract(1.Hours()).ToUniversalTime();
var action = theSession.Events.StartStream(streamId, new AEvent(), new BEvent(), new CEvent());
action.Events[0].UserName = "Kevin McHale";
await theSession.SaveChangesAsync();
using var query = theStore.QuerySession();
var events = await query.Events.FetchStreamAsync(streamId);
events[0].UserName.ShouldBe("Kevin McHale");
events[1].UserName.ShouldBe("Larry Bird");
events[2].UserName.ShouldBe("Larry Bird");
// Should write another test, but I'm doing it here!
var celtics = await query.Events.QueryAllRawEvents().Where(x => x.UserName == "Larry Bird").ToListAsync();
celtics.Count.ShouldBeGreaterThanOrEqualTo(2);
}
Summary
Projects like Marten are never, ever completed and we have no intentions of abandoning Marten anytime soon. The features above have been requested for quite awhile, but didn’t make the cut for Marten 8.0. I’m happy to see them hit now, and this could be the basis of a long waited formal support for efficient data imports to Marten from other event stores.
And of course, if there’s something that Marten or Wolverine doesn’t do today that you need, please reach out to JasperFx Software and we can talk about an engagement to build out your features.
It’s the halfway point of 2025 some how, and we’ve now gotten past the big Marten 8.0 and Wolverine 4.0 releases. Right before I go on vacation next week, I thought it would be a good time to jot down some thoughts about where the Critter Stack might go for the rest of 2025 and probably into 2026.
Critter Watch
The big ticket item is our ongoing work on “Critter Watch”, which will be a commercial management and observability add on for Wolverine, Marten, and any future new Critter tools. The top line pitch for Critter Watch is that it well help you know what your applications are, how they interact with each other, whether they’re healthy in production, and provide features to help heal the inevitable production problems when they happen.
The general idea is to have a standalone application deployed that acts as a management console for 1 or more Wolverine applications in our user’s environments:
Upfront for the Critter Watch MVP (and requests from a client), we’re focused on:
Visualizing the systems being monitored, their Wolverine and Marten configuration, and the capabilities of the systems. We’re currently researching AsyncAPI publishing and visualization as well. The whole point of this is to help teams understand how the messages in your system are handled, published, and routed.
Event Sourcing management, but this is mostly about managing the execution of asynchronous projections and subscriptions at runtime and being able to understand the ongoing performance or any ongoing problems
Dead letter queue management for Wolverine
I have less clarity over development time tooling, but we’re at least interested in having some of Critter Watch usable as an embedded tool during development.
After years of talking about this and quite a bit of envisioning, development started in earnest over the past 6 weeks with a stretch goal of having a pilot usage by the end of July for a JasperFx Software client.
I do not yet have any hard pricing numbers yet, but we are very interested in talking to anyone who would be interested in Critter Watch.
Concurrency, Concurrency, Concurrency!
I think that systems built with Event Sourcing are a little more sensitive to concurrent data reads and writes, or maybe it’s just that those problems are there all the time but more readily observable with Event Sourcing and Event Driven Architectures. In my work with JasperFx Software clients, concurrency is probably the most common subject of questions.
Mostly today you deal with this either by building in selective retry capabilities based on version conflict detection, or get fancier with queueing and message routing to eliminate the concurrent access as much as possible. Or both of course.
Which is great, but what if you’re not using Azure Service Bus? What if you’re only using local queueing? And wouldn’t it be nice if the existing Azure Service Bus FIFO support was a little less cumbersome to use in your code?
I don’t have a ton of detail, but there’s a range of internal proposals to create some new recipes for Wolverine usage to enable teams to more easily “shard” logical work between queues and within the local workers listening to queues to improve Wolverine’s handling of concurrent access without sacrificing parallel work and throughput or requiring repetitive code. Some of this is being done in collaboration with JasperFx clients.
Improving Wolverine’s Declarative Data Access
For lack of a better description, Wolverine has a feature set I’m heretofore calling “declarative data access” with the [Entity] attribute that triggers code generation within message handlers or HTTP endpoints to load requested data from Marten, EF Core, or RavenDb. And of course, there’s also what we call the “aggregate handler workflow” recipe for using the Decider pattern with Wolverine and Marten that I think is the simplest way to express business logic when using Event Sourcing in the .NET ecosystem.
To take these productivity features even farther, I think we’ll add some:
More control over what action to take if an entity is missing. Today, the HTTP endpoints will just return a 404 status code if required entities can’t be found. In future versions, we’ll let you customize log or ProblemDetails messages and have more control over how Wolverine generates the “if missing” path
At least for Marten, opt into Marten’s batch querying support if you are using more than one of any combination of the existing [Aggregate], [ReadAggregate], [Entity], or [Document] attributes to load data within a single HTTP endpoint or message handler as a way of improving performance by reducing network round trips to the database. And don’t sneeze at that, chattiness is a common performance killer in enterprise applications. Especially when the code is unnecessarily complicated by typical usages of Clean or Onion Architectural approaches.
If you follow Event Sourcing related topics online, you’ll hear quite a bit of buzz from some of the commercial tools about “Dynamic Consistency Boundaries” (DCB). We get asked about this with Marten occasionally, but the Marten core team’s position is that Marten doesn’t require this feature because you can already do “read” and “write” operations across multiple event streams with transactional integrity as is.
What the batch querying I just described will do for Marten though is make the full “Critter Stack” usage be more performant when you need to potentially work with more than one event stream at a time with all the transactional support and strong consistency that Marten (really PostgreSQL) already provides.
For Marten users, this is essentially making Marten’s FetchForWriting() API able to enroll in batch querying for more efficient data querying when working across streams. That work is actually well underway.
But if you prefer to use the fancier and more novel DCB approaches that aren’t even officially released yet, feel free to pay out some big bucks to use one of the commercial tools.
Smaller, But Still Important Work!
Partially for Critter Watch, Wolverine should support connecting to multiple brokers in a single application for each transport type. Some of this is already done, with Kafka being next up, but we need to add this to every transport
Improved interoperability support for Wolverine talking to non-Wolverine applications. There’s an existing pull request that goes quite a ways for this, but this might end up being more a documentation effort than anything else
More options in Wolverine with Marten or just Marten for streaming Marten data as JSON directly to HTTP. We have some support already of course, but there are more opportunities for expanding that
Exposing an MCP server off of Marten event data, but I have very little detail about what that would be. I would be very interested in partnering with a company who wanted to do this, and a JasperFx client might be working with us later this year on AI with Marten
Improving throughput in Marten’s event projections and subscriptions. We’ve done a lot the past couple years, but there are still some other ideas in the backlog we haven’t played yet
Expanding Wolverine support for more database engines, with CosmosDb the most likely contender this year. This might be contingent upon client work of course.
What about the SQL Server backed Event Store?
Yeah, I don’t know. We did a ton of work in Marten 8 to pull what will be common code out in a way that it could be reused in the SQL Server backed event store. I do not know when we might work on this as CritterWatch will take priority for now.
And finally….
And on that note I’m essentially on vacation for a week and I’ll catch up with folks in late July.
You’re about to start a new system with Event Sourcing using Marten, and you’re expecting your system to be hugely successful such that it’s going to handle a huge amount of data, but you’re already starting with pretty ambitious non-functional requirements for the system to be highly performant and all the screens or exposed APIs be snappy.
Basically, what you want to do is go as fast as Marten and PostgreSQL will allow. Fortunately, Marten has a series of switches and dials that can be configured to squeeze out more performance, but for a variety of historical reasons and possible drawbacks, are not the defaults for a barebones Marten configuration as shown below:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMarten(opts =>
{
opts.Connection(builder.Configuration.GetConnectionString("marten"));
});
Cut me some slack in my car choice for the analogy here. I’m not only an American, but I’m an American from a rural area who grew up dreaming about having my own Mustang or Camaro because that’s as far out as I could possibly imagine back then.
At this point, we have is the equivalent to a street legal passenger car, maybe the equivalent to an off the shelf Mustang:
Which probably easily goes fast enough for every day usage for the mass majority of us most of the time. But we really need a fully tricked out Mustang GTD that’s absurdly optimized to just flat out go fast:
Let’s start trimming weight off our street legal Marten setup to go faster with…
Opt into Lightweight Sessions by Default
Starting from a new system so we don’t care about breaking existing code by changing behavior, let’s opt for lightweight sessions by default:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMarten(opts =>
{
opts.Connection(builder.Configuration.GetConnectionString("marten"));
})
// Jettison some "Identity Map" weight by going lighter weight
.UseLightweightSessions();
By default, the instances of IDocumentSession you get out of an IoC container would utilize the Identity Map feature to track loaded entities by id so that if you happened to try to load the same entity from the same session, you would get the exact same object. As I’m sure you can imagine, that means that every entity fetched by a session is stuffed into a dictionary internally (Marten uses the highly performant ImTools ImHashMap everywhere, but still), and the session also has to bounce through the dictionary before loading data as well. It’s just a little bit of overhead we can omit by opting for “Lightweight Sessions” if we don’t need that behavior by default.
We’ve always been afraid to change the default behavior here to the more efficient approach because it can absolutely lead to breaking existing code that depends on the Identity Map behavior. On the flip side, I think you should not need Identity Map mechanics if you can keep the call stacks within your code short enough that you can actually “see” where you might be trying to load the same data twice or more in the same parent operation.
On to the next thing…
Make Writes Faster with Quick Append
Next, since we again don’t have any existing code that can be broken here, let’s opt for “Quick Append” writes like so:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMarten(opts =>
{
opts.Connection(builder.Configuration.GetConnectionString("marten"));
// Make event writing faster, like 2X faster in our testing
opts.Events.AppendMode = EventAppendMode.Quick;
})
// Jettison some "Identity Map" weight by going lighter weight
.UseLightweightSessions();
This will help the system be able to append new events much faster, but at the cost of not being able to use some event metadata like event versions, sequence, or timestamp information within “Inline” projections.
Again, even though this option has been clocked as being much faster, we have not wanted to make this the default because it could break existing systems for people who depend on having the rich metadata during the Inline application of projections that forces Marten to do a kind of two step process to append events. This “Quick Append” option also helps reduce concurrent access problems writing to streams and generally makes the “Async Daemon” subsystem processing asynchronous projections and subscriptions run much smoother.
We’re not out of tricks yet by any means, so let’s go on…
// This tells Wolverine that the first "return value" is NOT the response
// body
[EmptyResponse]
[WolverinePost("/api/incidents/{incidentId:guid}/category")]
public static IncidentCategorised Post(
// the actual command
CategoriseIncident command,
// Wolverine is generating code to look up the Incident aggregate
// data for the event stream with this id
[Aggregate("incidentId")] Incident incident)
{
// This is a simple case where we're just appending a single event to
// the stream.
return new IncidentCategorised(incident.Id, command.Category, command.CategorisedBy);
}
In the case above, the Incident model is a projected document that’s first used by the command handler to “decide” what new events to emit. If we’re updating the Incident model with an Inline projection that tries to update the Incident model in the database at the same time it wants to append events, then it’s an advantage for performance to “just” use the original Incident model we used initially, then forwarding the new state based on the new events and persisting the results right then and there. We can opt into this optimization even for the lightweight sessions we earlier wanted to use by adopting one more UseIdentityMapForAggregates flag:
builder.Services.AddMarten(opts =>
{
opts.Connection(builder.Configuration.GetConnectionString("marten"));
// Make event writing faster, like 2X faster in our testing
opts.Events.AppendMode = EventAppendMode.Quick;
// This can cut down on the number of database round trips
// Marten has to do during CQRS command handler execution
opts.Events.UseIdentityMapForAggregates = true;
})
// Jettison some "Identity Map" weight by going lighter weight
.UseLightweightSessions();
Note, this optimization can easily break code for folks who use some sort of stateful “Aggregate Root” approach where the state of the projected aggregate object might be mutated during the course of executing the command. As this has traditionally been a popular approach in Event Sourcing circles, we can’t make this be a default option. If you instead either make the projected aggregates like Incident either immutable or treat them as a dumb data input to your command handlers with a more Functional Programming “Decider” function approach, you can get away with the performance optimization.
And also, I strongly prefer and recommend the FP “Decider” approach to JasperFx Software clients as is and I think that folks using the older “Aggregate Root” approach tend to have more runtime bugs.
Moving on, let’s keep our database smaller…
Event Stream Archiving
By and large, you can improve system performance in almost any situation by trying to keep your database from growing too large by archiving or retiring obsolete information. Marten has first class support for “Archiving Event Streams” where you effectively just move event streams that only represent historical information and are not really active into an archived state.
Moreover, we can divide our underlying PostgreSQL storage for events into “hot” and “cold” storage by utilizing PostgreSQL’s table partitioning support like this:
builder.Services.AddMarten(opts =>
{
opts.Connection(builder.Configuration.GetConnectionString("marten"));
// Make event writing faster, like 2X faster in our testing
opts.Events.AppendMode = EventAppendMode.Quick;
// This can cut down on the number of database round trips
// Marten has to do during CQRS command handler execution
opts.Events.UseIdentityMapForAggregates = true;
// Let's leverage PostgreSQL table partitioning
// to our advantage
opts.Events.UseArchivedStreamPartitioning = true;
})
// Jettison some "Identity Map" weight by going lighter weight
.UseLightweightSessions();
If you’re aggressive with marking event streams as Archived, the PostgreSQL table partitioning can move off archived event streams into a different table partition than our active event data. This is essentially keeping the “active” event table storage relatively stable in size, and most operations will execute against this smaller table partition while still being able to access the archived data too if explicitly opt into including that.
We added this feature in a minor point 7.* release, so it had to be opt in, and I think I was too hesitant to make this a default in 8.0, so it’s still “opt in”.
Stream Compacting
Beyond archiving event streams, maybe you just want to “compact” a longer event stream so you technically retain all the existing state, but further reduce the size of your active database storage. To that end, Marten 8.0 added Stream Compacting.
Distributing Asynchronous Projections
I had been mostly talking about using projections running Inline such that the projections are updated at the same time as the events are captured. That’s sometimes applicable or desirable, but other times you’ll want to optimize the “write” operations by moving the updating of projected data to an Async projection running in the background. But now let’s say that we have quite a few asynchronous projections and several subscriptions as well. In early versions of Marten, we had to run everything in a “Hot/Cold” mode where every known projection or subscription had to run on one single “leader” node. So even if you were running your application across a dozen or more nodes, only one could be executing all of the asynchronous projections and subscriptions.
That’s obviously a potential bottleneck, so Marten 7.0 by itself introduced some ability to spread projections and subscriptions over multiple nodes. If we introduce Wolverine into the mix though, we can do quite a bit better than that by allowing Wolverine to distribute the asynchronous Marten work across our entire cluster with its ability to distribute Marten projections and subscriptions with the UseWolverineManagedEventSubscriptionDistribution option in the WolverineFx.Marten Nuget:
builder.Services.AddMarten(opts =>
{
opts.Connection(builder.Configuration.GetConnectionString("marten"));
// Make event writing faster, like 2X faster in our testing
opts.Events.AppendMode = EventAppendMode.Quick;
// This can cut down on the number of database round trips
// Marten has to do during CQRS command handler execution
opts.Events.UseIdentityMapForAggregates = true;
// Let's leverage PostgreSQL table partitioning
// to our advantage
opts.Events.UseArchivedStreamPartitioning = true;
})
// Jettison some "Identity Map" weight by going lighter weight
.UseLightweightSessions()
.IntegrateWithWolverine(opts =>
{
opts.UseWolverineManagedEventSubscriptionDistribution = true;
});
Is there anything else for the future?
It never ends, and yes, there are still quite a few ideas in our product backlog to potentially improve performance and scalability of Marten’s Event Sourcing. Offhand, that includes looking at alternative, higher performance serializers and more options to parallelize asynchronous projections to squeeze out more throughput by sharing some data access across projections.
Summary
There are quite a few “opt in” features in Marten that will help your system perform better, but these features are “opt in” because they can be harmful if you’re not building around the assumptions these features make about how your code works. The good news though is that you’ll be able to better utilize these features if you follow the Critter Stack’s recommended practices by striving for shorter code stacks (i.e., how many jumps between methods and classes does your code make when receiving a system input like a message or HTTP request) so your code is easier to reason about anyway, and avoiding mutating projected aggregate data outside of Marten.
Lamar, the spiritual successor to StructureMap, had a corresponding 15.0 release
And underneath those tools, the new JasperFx & JasperFx.Events library went 1.0 and the supporting Weasel library that provides some low level functionality went 8.0
Before getting into the highlights, let me start by thanking the Critter Stack Core team for all their support, contributions to both the code and documentation, and for being a constant sounding board for me and source of ideas and advice:
Next, I’d like to thank our Critter Stack community for all the interest and the continuous help we get with suggestions, pull requests that improve the tools, and especially for the folks who take the time to create actionable bug reports because that’s half the battle of getting problems fixed. And while there are plenty of days when I wish there wasn’t a veritable pack of raptors prowling around the projects probing for weaknesses in the projects, I cannot overstate the importance for an OSS project to have user and community feedback.
Alright, on to some highlights.
The big changes are that we consolidated several smaller shared libraries into one bigger shared JasperFx library and also combined some smaller libraries like Marten.CommandLine, Weasel.CommandLine, and Lamar.Diagnostics into Marten, Weasel, and Lamar respectfully. That’s hopefully going to help folks get to command line utilities quicker and easier, and the Critter Stack tools do get some value out of those command line utilities.
We’ve now got a shared model to configure behavioral differences at “Development” vs “Production” time for both Marten and Wolverine all at one time like this:
// These settings would apply to *both* Marten and Wolverine
// if you happen to be using both
builder.Services.CritterStackDefaults(x =>
{
x.ServiceName = "MyService";
x.TenantIdStyle = TenantIdStyle.ForceLowerCase;
// You probably won't have to configure this often,
// but if you do, this applies to both tools
x.ApplicationAssembly = typeof(Program).Assembly;
x.Production.GeneratedCodeMode = TypeLoadMode.Static;
x.Production.ResourceAutoCreate = AutoCreate.None;
// These are defaults, but showing for completeness
x.Development.GeneratedCodeMode = TypeLoadMode.Dynamic;
x.Development.ResourceAutoCreate = AutoCreate.CreateOrUpdate;
});
It might be awhile before this pays off for us, but everything from the last couple paragraphs is also meant to speed up the development of additional Event Sourcing “Critter” tools to expand beyond PostgreSQL — not that we’re even slightly backing off our investment in the do everything PostgreSQL database!
For Wolverine 4.0, we’ve improved Wolverine’s ability to support modular monolith architectures that might utilize multiple Marten stores or EF Core DbContext services targeting the same database or even different databases. More on this soon.
Both Wolverine and Marten got some streamlined Open Telemetry span naming changes that were suggested by Pascal Senn of ChiliCream who collaborates with JasperFx for a mutual client.
For both Wolverine and Lamar 15, we added a little more full support for the [FromKeyedService] and “keyed services” in the .NET Core DI abstractions like this for a Wolverine handler:
// From a test, just showing that you *can* do this
// *Not* saying you *should* do that very often
public static void Handle(UseMultipleThings command,
[FromKeyedServices("Green")] IThing green,
[FromKeyedServices("Red")] IThing red)
{
green.ShouldBeOfType<GreenThing>();
red.ShouldBeOfType<RedThing>();
}
And inside of Lamar itself, any dependency from a constructor function that has this:
// Lamar will inject the IThing w/ the key "Red" here
public record ThingUser([FromKeyedServices("Red")] IThing Thing);
Granted, Lamar already had its own version of keyed services and even an equivalent to the [FromKeyedService] attribute long before this was added to the .NET DI abstractions and ServiceProvider conforming container, but .NET is Microsoft’s world and lowly OSS projects pretty well have to conform to their abstractions sometimes.
Just for the record, StructureMap had an equivalent to keyed services in its first production release way back in 2004 back when David Fowler was probably in middle school making googly eyes at Rihanna.
What’s Next for the Critter Stack?
Honestly, I had to cut some corners on documentation to get the releases out for a JasperFx Software client, so I’ll be focused on that for most of this week. And of course, plenty of open issues and some outstanding pull requests didn’t make the release, so those hopefully get addressed in the next couple minor releases.
For the bigger picture, I think the rest of this year is:
“CritterWatch”, our long planned, not moving fast enough for my taste, management and observability console for both Marten and Wolverine.
Improvements to Marten’s performance and scalability for Event Sourcing. We did a lot in that regard last year throughout Marten 7.*, but there’s another series of ideas to increase the throughput even farther.
Wolverine is getting a lot of user contributions right now, and I expect that especially the asynchronous messaging support will continue to grow. I would like to see us add CosmosDb support to Wolverine by the end of the year. By and large, I would like to increase Wolverine’s community usage over all by trying to grow the tool beyond just folks already using Marten — but the Marten + Wolverine combination will hopefully continue to improve.
More Critters? We’re still talking about a SQL Server backed Event Store, with CosmosDb being a later alternative
Wrapping Up
As for the wisdom of ever again making a release cycle where the entire Critter Stack has a major release at the exact same time, this:
Finally, a lot of things didn’t make the release that folks wanted, heck that I wanted, but at some point it becomes expensive for a project to have a long running branch for “vNext” and you have to make the release. I’m hopeful that even though these major releases didn’t add a ton of new functionality that they set us up with the right foundation for where the tools go next.
I also know that folks will have plenty of questions and probably even inevitably run into problems or confusion with the new releases — especially until we can catch up on documentation — but I stole time from the family to get this stuff out this weekend and I’ll probably not be able to respond to anyone but JasperFx customers on Monday. Finally, in the meantime, right after every big push, I promise to start responding to whatever problems folks will have, but:
One of the earliest lessons I learned designing software systems is that reigning in unchecked growth of databases through judicious pruning and archiving can do wonders for system performance over time. As yet another tool in the toolbox for scaling Marten and in collaboration with a JasperFx Software customer, we’re adding an important feature in Marten 8.0 called “Stream Compacting” that can be used to judiciously shrink Marten’s event storage to keep the database a little more limber as old data is no longer relevant.
Let’s say that you failed to be omniscient in your event stream modeling and ended up with a longer stream of events than you’d ideally like and that is bloating your database size and maybe impacting performance. Maybe you’re going to be in a spot where you don’t really care about all the old events, but really just want to maintain the current projected state and more recent events. And maybe you’d like to throw the old events in some kind of “cold” storage like an S3 bucket or [something to be determined later].
Enter the new “Stream Compacting” feature that will come with Marten 8.0 next week like so:
public static async Task compact(IDocumentSession session, Guid equipmentId, IEventsArchiver archiver)
{
// Maybe we have ceased to care about old movements of a piece of equipment
// But we want to retain an accurate positioning over the past year
// Yes, maybe we should have done a "closing the books" pattern, but we didn't
// So instead, let's just "compact" the stream
await session.Events.CompactStreamAsync<Equipment>(equipmentId, x =>
{
// We could say "compact" all events for this stream
// from version 1000 and below
x.Version = 1000;
// Or instead say, "compact all events older than 30 days ago":
x.Timestamp = DateTimeOffset.UtcNow.Subtract(30.Days());
// Carry out some kind of user defined archiving process to
// "move" the about to be archived events to something like an S3 bucket
// or an Azure Blob or even just to another table
x.Archiver = archiver;
// Pass in a cancellation token because this might take a bit...
x.CancellationToken = CancellationToken.None;
});
}
What this “compacting” does is effectively create a snapshot of the stream state (the Equipment type in the example above) and replaces the existing events that are archived in the database with a single Compacted<Equipment> event with this shape:
// Right now we're just "compacting" in place, but there's some
// thought to extending this to what one of our contributors
// calls "re-streaming" in their system where they write out an
// all new stream that just starts with a summary
public record Compacted<T>(T Snapshot, Guid PreviousStreamId, string PreviousStreamKey)
The latest, greatest Marten projection bits are always able to restart any SingleStreamProjection with the Snapshot data of a Compacted<T> event, with no additional coding on your part.
And now, to answer a few questions that my client (Carsten, this one’s for you, sorry I was slow today:)) asked me about this today:
Is there going to be a default archiver? Not yet, but I’m all ears on what that could or should be. It’ll always be pluggable of course because I’d expect a wide range of usages
How about async projections? This will not impact asynchronous projections that are already in flight. The underlying mechanism is not using any persisted, projected document state but is instead fetching the raw events and effectively doing a live aggregation to come back to the compacted version of the projected document.
Can you compact a single stream multiple times? Yes. I’m thinking folks could use a projection “side effect” to emit a request message to compact a stream every 1,000 events or some other number.
What happens in case the async daemon moves beyond (e.g. new events were saved while the compacting is ongoing) – will the compacting aggregation overwrite the projection updates done by the async daemon – basically the same for inline projections? The compacting will be done underneath the async daemon, but will not impact the daemon functionality. The projections are “smart enough” to restart the snapshot state from any Compacted<T> event found in the middle of the current events anyway.
How does rewind and replay work if a stream is compacted? Um, you would only be able to replay at or after the point of compacting. But we can talk about making this able to recover old events from archiving in a next phase!
Any other limitations? Yeah, same problem we ran into with the “optimized rebuild” feature from Marten 7.0. This will not play well if there are more than one single stream projection views for the same type of stream. Not insurmountable, but definitely not convenient. I think you’d have to explicitly handle a Compacted<T1> event in the projection for T2 if both T1 and T2 are separate views of the same stream type.
Why do I care? You probably don’t upfront, but this might easily be a way to improve the performance and scalability of a busy system over time as the database grows.
Is this a replacement or alternative to the event archival partitioning from Marten 7? You know, I’m not entirely sure, and I think your usage may vary. But if your database is likely to grow massively large over time and you can benefit from shrinking the size of the “hot” part of the database of events you no longer care about, do at least one or both of these options!
Summary
The widespread advice from event sourcing experts is to “keep your streams short”, but I also partially suspect this is driven by technical limitations of some of the commonly used, early commercial event store tools. I also believe that Marten is less impacted by long stream sizes than many other event store tools, but still, smaller databases will probably outperform bigger ones in most cases.
Time for an update on Critter Stack release plans, and a follow up on my previous Critter Stack Work in Progress post from March. The current plan is to release Marten 8.0, Weasel 8.0, and Wolverine 4.0 on June 1st. It’s not going to be a huge release in terms of new functionality, but there are some important structural changes that will help us build some future features, and we needed to jettison older .NET versions while getting onto the latest Npgsql. “CritterWatch” is still very much planned and a little bit in progress, but we’ve got to get these big releases out first.
The key takeaways are that I want to essentially freeze Marten 7.* for everything but bug fixes right now, and probably freeze Wolverine 3.* for new feature development after a last wave of pull requests gets pulled in over the next couple days.
I’m admittedly too drowsy and lazy to write much tonight, so here’s just a dump of what I wrote up for the rest of our core team to review. I think we’re already at the point where we’re ready to just work on documentation and a few last touches, so the mass majority of this doesn’t get done in time, but here’s the full brain storm:
First though, what’s been done:
.NET 6 & 7 were dropped
Updated to Npgsql 9 across the board
Dropped all synchronous APIs in Marten
Deleted some [Obsolete] APIs in Marten
Consolidation of supporting libraries to a single JasperFx library
JasperFx has that new consolidated configuration option for common configuration like application assembly, code generation, and the stateful resource AutoCreate mode
Pulled out event projections and core event store abstractions to a new JasperFx.Events library
Removed code generation from all projections
Better explicit code options for aggregations and event projections
Wolverine 4 has better handles envelope storage & the transactional inbox/outbox for modular monoliths
Improved “Descriptor” model to describe the static configuration of Wolverine and/or Marten applications that we’ll use for CritterWatch too
Expanded commands for dead letter queue management in Wolverine that was meant for CritterWatch
Multi-tenancy options in Wolverine for SQL Server or PostgreSQL w/o Marten, multi-tenancy usage with EF Core
Punchlist?
Marten 7.40.4 release w/ a pair of outstanding PRs
Cherry pick commits to Marten “master”
JasperFx & JasperFx.Events 1.0
Documentation website?
Weasel “master” branch
All tests should be passing
Marten “master” branch
All tests should be passing
Documentation website should be building – that’s going to take some effort because of code samples
Get Anne’s PR for tutorials in (cool new guided tour of building a system using Event Sourcing and Event Driven Architecture with first Marten, then Wolverine)
Stream Compacting feature – for a JasperFx customer (this is definitely in for Marten 8, this is a big improvement for keeping a larger system running fast over time by compacting the database)
Fix the optimized projection rebuild options? Or rip it out and leave it for CritterWatch?
Figure out what the proper behavior of “Live” aggregations when there’s some ShouldDelete() action going on
Wolverine
One last 3.14 release with easy to grab pull requests and bug fixes
Rebase on 3.14
Fork off the 3.0 branch
4.0 becomes main branch
All tests should be passing
Documentation website should build
Migration guide
Critter Watch preparation
When integrated w/ CritterWatch, Wolverine can build the descriptor model for the entire application, including EventStoreUsage. No idea where this work stands right now. Did quite a bit earlier this year, then went off in a different direction
Review all Open Telemetry usage and activity naming across Marten and especially Wolverine. Add Open Telemetry & Metrics metadata to the descriptor model sent to CritterWatch. I think this is somewhat likely to get done before Wolverine 4.0.
Ability to send messages from CritterWatch to Wolverine. Might push through some kind of message routing and/or message handler extensibility
Programmatic message routing in Wolverine that varies based on the message contents? This is around options to route a message to one of a set of destinations based on the message core. Thinking about concurrency here. Could be done later.
More open issues in the Marten 8 milestone, but it’s about time to drop any issue that isn’t a breaking change
As I wrote last week, message or request concurrency is probably the single most common source of client questions in JasperFx Software consulting and support work around the Critter Stack. Wolverine is a powerful tool for command and event message processing, and it comes with a lot of built in options for wide range of usage scenarios that provider the answers for a lot of the questions we routinely field from clients and other users. More specifically, Wolverine provides a lot of adjustable knobs to limit or expand:
Message processing parallelism. I.e., how many messages can be executed simultaneously
Message ordering when you need messages to be processed in sequence, or a lack thereof when you don’t
For better or worse, Wolverine has built up quite a few options over the years, and that can be admittedly confusing. Also, there are real performance or correctness tradeoffs with the choices you make around message ordering and processing parallelism. To that end, let’s go through a little whirlwind tour of Wolverine’s options for concurrency, parallelism, and delivery guarantees.
Listener Endpoints
Note that Wolverine standardizes the fluent interface options for endpoint type, message ordering, and parallel execution are consistent across all of its messaging transport types (Rabbit MQ, Azure Service Bus, Kafka, Pulsar, etc.), though not every option is available for every transport.
All messages handled in a Wolverine application come from a constantly running listener “Endpoint” that then delegates the incoming messages to the right message handler. A Wolverine “Endpoint” could be a local, in process queue, a Rabbit MQ queue, a Kafka topic, or an Azure Service Bus subscription (see Wolverine’s documentation on asynchronous messaging for the entire list of messaging options).
This does vary a bit by messaging broker or transport, but there are three modes for Wolverine endpoints, starting with Inline endpoints:
// Configuring a Wolverine application to listen to
// an Azure Service Bus queue with the "Inline" mode
opts.ListenToAzureServiceBusQueue(queueName, q => q.Options.AutoDeleteOnIdle = 5.Minutes()).ProcessInline();
With an Inline endpoint, messages are pulled off the receiving queue or topic one message at a time, and “ack-ed” back to the original queue or topic only on the successful completion of the message handler. This mode completely eschews any kind of durable, transactional inbox, but does still give you an at-least-once delivery guarantee as it’s possible that the “ack” process could fail after the message is successfully handled, potentially resulting in the message being resent from the external messaging broker. Know though that this is rare, and Wolverine puts some error retries around the “ack-ing” process.
As you would assume, using the Inline mode gives you sequential processing of messages within a single node, but limits parallel handling. You can opt into running parallel listeners for any given listening endpoint:
opts.ListenToRabbitQueue("inline")
// Process inline, default is with one listener
.ProcessInline()
// But, you can use multiple, parallel listeners
.ListenerCount(5);
The second endpoint mode is Buffered where messages are pulled off the external messaging queue or topic as quickly as they can be, and immediately put into an in memory queue and “ack-ed” to any external broker.
// I overrode the buffering limits just to show
// that they exist for "back pressure"
opts.ListenToAzureServiceBusQueue("incoming")
.BufferedInMemory(new BufferingLimits(1000, 200));
In the sample above, I’m showing how you can override the defaults for how many messages can be buffered in memory for this listening endpoint before the endpoint is paused. Wolverine has some support for back pressure within its Buffered or Durable endpoints to prevent memory from being overrun.
With Buffered or the Durable endpoints I’ll describe next, you can specify the maximum number of parallel messages that can be processed at one time within a listener endpoint on a single node like this:
Or you can choose to run messages in a strict sequential order, one at a time like this:
// Make any kind of Wolverine configuration
options
.PublishMessage<Module1Message>()
.ToLocalQueue("module1-high-priority")
.Sequential();
The last endpoint type is Durable, which behaves identical to the Buffered approach except that messages received from external message brokers are persisted to a backing database first before processing, then deleted when the messages are successfully processed or discarded or moved to dead letter queues by error handling policies:
Using the Durable mode enrolls the listening endpoint into Wolverine’s transactional inbox. This is the single most robust option for delivery guarantees with Wolverine, and even adds some protection for idempotent receipt of messages such that Wolverine will quietly reject the same message being received multiple times. Durable endpoints are more robust in terms of delivery guarantees and resilient in the face of system hiccups than the Buffered mode, but does incur a little bit of extra overhead making calls to a database — but I should mention that Wolverine is trying really hard to batch up calls to the database whenever it can for better runtime efficiency, and there are retry loops in all the internals for resiliency as well.
If you really read this post you should hopefully be badly abused of the flippant advice floating around .NET circles right now after the MassTransit commercialization announcement that you can “just” write your own abstractions over messaging brokers instead of using a robust, off the shelf toolset that will have far more engineering for resiliency and observability than most folks realize.
Scenarios
Alright, let’s talk about some common messaging scenarios and look at possible Wolverine options. It’s important to note that there is some real tension between throughput (how many messages can you process over time), message ordering requirements, and delivery guarantees and I’ll try to call those compromises as we go.
You have a constant flood of small messages coming in that are relatively cheap to process…
In this case I would choose a Buffered endpoint and allow it to run messages in parallel:
Letting messages run without any strict ordering will allow the endpoint to process messages faster. Using the Buffered approach will allow the endpoint to utilize any kind of message batching that external message brokers might support, and does a lot to remove the messaging broker as a bottle neck for message processing. The Buffered approach isn’t durable of course, but if you care about throughput more than guarantees or message ordering, it’s the best option.
Note that any Buffered or Durable endpoint automatically allows for parallel message processing capped by the number of processor cores for the host process.
A message is expensive to process…
If you have a message type that turns out to require a lot of resources to process, you probably want to limit the parallelization to restrict how many resources the system uses for this message type. I would say to either use an Inline endpoint:
opts.ListenToRabbitQueue("expensive")
// Process inline, default is with one listener
.ProcessInline()
// Cap it to no more than two messages in parallel at any
// one time
.ListenerCount(2);
or a Buffered or Durable endpoint, but cap the parallelization.
Messages should be processed in order, at least on each node…
Use either a ProcessInline endpoint, or use the Sequential() option on any other kind of endpoint to limit the local processing to single file:
A certain type of message should be processed in order across the entire application…
Sometimes there’s a need to say that a certain set of messages within your system need to be handled in strict order across the entire application. While some specific messaging brokers have some specific functionality for this scenario, Wolverine has this option to ensure that a listening endpoint for a certain location only runs on a single node within the application at any one time, and always processes in strict sequential order:
var host = await Host.CreateDefaultBuilder().UseWolverine(opts =>
{
opts.UseRabbitMq().EnableWolverineControlQueues();
opts.PersistMessagesWithPostgresql(Servers.PostgresConnectionString, "listeners");
opts.ListenToRabbitQueue("ordered")
// This option is available on all types of Wolverine
// endpoints that can be configured to be a listener
.ListenWithStrictOrdering();
}).StartAsync();
Watch out of course, because this throttles the processing of messages to single file on exactly one node. That’s perfect for cases where you’re not too concerned about throughput, but sequencing is very important. A JasperFx Software client is using this for messages to a stateful Saga that coordinates work across their application.
Do note that Wolverine will both ensure a listener with this option is only running on one node, and will redistribute any strict ordering listeners to better distribute work across a cluster. Wolverine will also be able to detect when it needs to switch the listening over to a different node if a node is taken down.
Messages should be processed in order within a logical group, but we need better throughput otherwise…
Let’s say that you have a case where you know the system would work much more efficiently if Wolverine could process messages related to a single business entity of some sort (an Invoice? a Purchase Order? an Incident?) in strict order. You still need more throughput than you can achieve through a strictly ordered listener that only runs on one node, but you do need the messages to be handled in order or maybe just one at a time for a single business entity to arrive at consistent state or to prevent errors due to concurrent access.
_host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
opts.UseAzureServiceBusTesting()
.AutoProvision().AutoPurgeOnStartup();
opts.ListenToAzureServiceBusQueue("send_and_receive");
opts.PublishMessage<AsbMessage1>().ToAzureServiceBusQueue("send_and_receive");
opts.ListenToAzureServiceBusQueue("fifo1")
// Require session identifiers with this queue
.RequireSessions()
// This controls the Wolverine handling to force it to process
// messages sequentially
.Sequential();
opts.PublishMessage<AsbMessage2>()
.ToAzureServiceBusQueue("fifo1");
opts.PublishMessage<AsbMessage3>().ToAzureServiceBusTopic("asb3");
opts.ListenToAzureServiceBusSubscription("asb3")
.FromTopic("asb3")
// Require sessions on this subscription
.RequireSessions(1)
.ProcessInline();
}).StartAsync();
But, there’s a little bit more to publishing because you’ll need to tell Wolverine what the GroupId value is for your message:
I think we’ll try to make this a little more automatic in the near future with Wolverine.
// bus is an IMessageBus
await bus.SendAsync(new AsbMessage3("Red"), new DeliveryOptions { GroupId = "2" });
await bus.SendAsync(new AsbMessage3("Green"), new DeliveryOptions { GroupId = "2" });
await bus.SendAsync(new AsbMessage3("Refactor"), new DeliveryOptions { GroupId = "2" });
Of course, if you don’t have Azure Service Bus, you still have some other options. I think I’m going to save this for a later post, hopefully after building out some formal support for this, but another option is to:
Plan on having several different listeners for a subset of messages that all have the strictly ordered semantics as shown in the previous section. Each listener can at least process information independently
Use some kind of logic that can look at a message being published by Wolverine and use some kind of deterministic rule that will assign that message to one of the strictly ordered messaging destinations
Like I said, more to come on this in the hopefully near future, and this might be part of a JasperFx Software engagement soon.
What about handling events in Wolverine that are captured to Marten (or future Critter Event Stores)?
I’m Gen X, so the idea of Marten & Wolverine assembling to create the ultimate Event Driven Architecture stack makes me think of Transformers cartoons:)
It’s been a few years, but what is now Wolverine was originally called “Jasper” and was admittedly a failed project until we decided to reorient it to being a complement to Event Sourcing with Marten and renamed it “Wolverine” to continue the “Critter Stack” theme. A huge part of that strategy was having first class mechanisms to either publish or handle events captured by Marten’s Event Sourcing through Wolverine’s robust message execution and message publishing capabilities.
You have two basic mechanisms for this. The first, and original option is “Event Forwarding” where events captured by Marten are published to Wolverine upon the successful completion of the Marten transaction:
builder.Services.AddMarten(opts =>
{
var connString = builder
.Configuration
.GetConnectionString("marten");
opts.Connection(connString);
// There will be more here later...
opts.Projections
.Add<AppointmentDurationProjection>(ProjectionLifecycle.Async);
// OR ???
// opts.Projections
// .Add<AppointmentDurationProjection>(ProjectionLifecycle.Inline);
opts.Projections.Add<AppointmentProjection>(ProjectionLifecycle.Inline);
opts.Projections
.Snapshot<ProviderShift>(SnapshotLifecycle.Async);
})
// This adds a hosted service to run
// asynchronous projections in a background process
.AddAsyncDaemon(DaemonMode.HotCold)
// I added this to enroll Marten in the Wolverine outbox
.IntegrateWithWolverine()
// I also added this to opt into events being forward to
// the Wolverine outbox during SaveChangesAsync()
.EventForwardingToWolverine();
Event forwarding gives you no ordering guarantees of any kind, but will push events as messages to Wolverine immediately. Event forwarding may give you significantly better throughput then the subscription model we’ll look at next because there’s less latency between persisting the event to Marten and the event being published to Wolverine. Moreover, using “Event Forwarding” means that the event publishing happens throughout any application cluster.
However, if you need strictly ordered handling of the events being persisted to Marten, you instead need to use the Event Subscriptions model where Wolverine is handling or relaying Marten events as messages in the strict order in which they are appended to Marten, and on a single running node. This is analogous to the strictly ordered listener option explained above.
There’s a real tradeoff between message ordering, processing throughput, and message delivery guarantees. Fortunately, Wolverine gives you plenty of options to meet a variety of different project requirements.
And one last time, you’re just not going to want to sign up for the level of robust options and infrastructure that’s under the covers of a tool like Wolverine can “just roll your own messaging abstractions” because you’re angry and think that community OSS tools can’t be trusted. And also, Wolverine is also a moving target that constantly improves based on the problems, needs, suggestions, and code contributions from our core team, community, and JasperFx Software customers. Your homegrown tooling will never receive that level of feedback, and probably won’t ever match Wolverine’s quality of documentation either.