It’s apparently time to play another game of .NET developers feeling consternation because of an OSS project’s attempt to be more sustainable. JasperFx Software, the company I founded around the “Critter Stack” tools is committed to an “Open Core” model. What that means for us is that:
The main libraries and tools like Marten and Wolverine will remain MIT licensed, i.e. free and open
JasperFx also has its commercial AI Skills offerings for the Critter Stack and now our commercial CritterWatch tool. With another set of commercial tools related to AI assisted development and Event Modeling coming soon (those will be part of the same license as CritterWatch)
At this point I think that JasperFx Software has already established that we have a viable business model and that we’re going to be able to sustain our “Open Core” model going forward.
We still get occasional friction from potential clients and users that we’ll do the same OSS “rug pull” that some other projects did by adopting a commercial license on their newer versions even though I’ve said “Open Core” in public a half a thousand times. I do not particularly enjoy that level of cynicism about OSS that frequently crops up in the ,NET community.
What I would say to folks out there is that tools like Marten and Wolverine are just not viable as side projects. A huge amount of our functionality in Marten for scalability only existed after I founded JasperFx and started working directly with clients every day. Wolverine’s leadership election which undergirds a lot of our advanced blue/green deployment support and scalability would not have been possible to build and constantly curate without me being full time on the Critter Stack. I would ask our users to have some awareness for how much time it takes to evolve and maintain their OSS tools.
Yes, the existence of AI tools makes it tempting to think you can just vibe code replacements for your 3rd party dependencies over a rainy weekend, but you have to also understand how much hardening widely used OSS tools get from being beaten up by users and having to adapt to a world of technical irregularities like database outages, Rabbit MQ quietly dropping connections, database overloading, network hiccups, database administrators unexpectedly sending a kill signal to a PostgreSQL database that turns out to create gaps in sequences (and wasn’t that one fun), and not to mention all the crazy edge cases we’ve had to face from Kubernetes doing Kubernetes things.
To sum this all up, you can’t just vibe code replacements for quite a bit of this, these kinds of tools achieve deep quality through a lot of usage, feedback, and adaptation over time — and all of that takes a lot of time and a long attention span.
Hell, I’ve personally had to make several improvements to code subsystems in Marten and Wolverine in the last month that I thought were “done” and as stable as they could possibly be because new users in new circumstances proved otherwise
And just because I might get asked about this, my friend Ian Cooper wrote about this too, but maybe coming from a different perspective as an OSS maintainer. I partially agree with some of that and I’ll respectfully disagree with other parts and just leave it at that.
I’m obviously sympathetic to the Polly maintainers, and based on this exchange with one of the creators of the OSMF, my initial inclination is to pay the OSMF fee from JasperFx Software because of our commercialization of Marten, Polecat, and Fisherthrough support plans (those projects are still MIT licensed folks!) and the transitive dependency that CritterWatch has on Polly through those other libraries:
Like I said, I’m sympathetic to the Polly maintainers, and we’ll try to be above board with them here. But, if there’s even the slightest bit of hesitation from our current or potential customers about the Polly license, we’ll replace our relatively small usage of Polly with something new in our foundational JasperFx library and remove Polly entirely. I’m not enthusiastic about doing that because the Polly.Core dependency is in our public API and pulling that out would require us to either do a major version release or cheat on SemVer rules — which we really hate to do without very good reason.
I after all have a fiduciary responsibility to my “shareholders” to make JasperFx a sustainable financial success.
Wolverine has its own resiliency features, so Polly isn’t a concern there at least. Our document database and event store applications do use Polly for resiliency against transient errors though, and that’s what would need to change. I’d guess that most of our users don’t even realize that’s there, so maybe the switchover won’t be that big a deal if we decide to go that way.
This is a potentially big performance optimization you can opt into starting with Marten 9.0. Not coincidentally, we’re using this for CritterWatch to help optimize the responsiveness and database size for a JasperFx client this week.
Marten can serialize individual event types to a binary wire format (MemoryPack, MessagePack, or anything else implementing IEventBinarySerializer) instead of the default JSON, trading a few of JSON’s ergonomic wins for a meaningful throughput and storage-size improvement on hot streams. See #4515 for the design discussion.
The opt-in is per event type — binary-serialized and JSON-serialized events coexist in the same mt_events table, so the feature can be rolled out on an existing store with no migration of existing data.
How it works
A second column, bdata bytea NULL, sits alongside the existing data jsonb NOT NULL on mt_events. The row-level discriminator is bdata IS NULL:
When
data
bdata
Event uses the JSON serializer
full JSON payload
NULL
Event uses an IEventBinarySerializer
the placeholder '{}'::jsonb
the serialized bytes
On read, Marten inspects bdata:
NULL → existing JSON deserialization path. Pre-feature rows continue to work without conversion.
Because the discriminator is on the row and the serializer is resolved per event type, the same stream can carry rows of either format with no special handling at the call site.
Quick start with Marten.MemoryPack
The companion Marten.MemoryPack NuGet package ships a ready-to-use IEventBinarySerializer over MemoryPack:
dotnet add package Marten.MemoryPack
Mark event types you want to serialize as binary with both [BinaryEvent] (so Marten picks them up) and [MemoryPackable] (so MemoryPack can serialize them):
If a type carries [BinaryEvent] but no per-type serializer was wired AND DefaultBinarySerializer is null, Marten throws at the first append with a remediation message naming both registration entry points.
Bring your own serializer
IEventBinarySerializer is small enough to implement directly against any binary format — MessagePack, protobuf, etc.:
publicinterfaceIEventBinarySerializer
{
byte[] Serialize(Typetype, objectdata);
objectDeserialize(Typetype, byte[] data);
}
The serializer is a singleton — keep its state thread-safe.
On-disk shape
For binary events, data holds the literal {} placeholder so the existing data jsonb NOT NULL constraint stays intact (no schema relaxation):
Purely additive: the only schema change is bdata bytea NULL on mt_events. Existing rows have bdata = NULL (the column’s default for prior data) and read through the JSON path. Marten’s standard schema migration creates the column for existing installations — no event data conversion required.
Append modes
Binary event serialization works with everyEventAppendMode Marten ships — Rich, Quick, and QuickWithServerTimestamps. The Quick modes route appends through the mt_quick_append_events PostgreSQL function, which carries a bdatas bytea[] parameter that’s inserted into mt_events.bdata in parallel with the existing bodies jsonb[]. BulkEventAppender (the COPY-based bulk loader) also supports binary events — its COPY column list includes bdata, and each event row writes either the binary payload or NULL.
You don’t have to think about the append mode: binary opt-in is per event type and works identically across all of them.
Schema evolution — use versioned event types
Marten’s existing event upcasters operate on the JSON wire form and don’t generalize to a byte[] payload, so they don’t apply to binary events. The recommended pattern for evolving a binary event’s shape is introduce a new event type for each version rather than upcasting in place:
The coexistence design lets old rows (written as TripStarted) and new rows (written as TripStartedV2) live on the same stream without migration.
Why not in-place backward-compatible schema changes?
You can lean on MemoryPack’s backward-compatible field evolution ([MemoryPackOrder], nullable fields, the VersionTolerant mode) for additive-only changes to a single event type. That works as long as the serializer itself can deserialize old payloads into the new shape — but the moment a change goes beyond the serializer’s tolerance rules (renaming, type changes, splitting a field), there’s no JSON-style upcaster path to fall back on. Versioning the event type works for every shape of change and stays explicit about which version each row was written with.
Mixing binary + JSON
If you have an existing JSON-serialized event and want a future version to go binary, the same pattern applies: define a new [BinaryEvent]-marked type for the new version, leave the old (JSON) type and its upcasters alone, and have the aggregate handle both. The per-row dispatch already copes with mixed formats on the same stream.
I’m taking a little bit of time today to start writing up a “strategery” document on the JasperFx / Critter Stack approach to AI in the near future. One of the arguments I’m wanting to lean into quite heavily to call Wolverine AI-friendly is how it compresses code in its approach to “Vertical Slice Architecture” and how its focus on the “A-Frame Architecture” to testability has also made Wolverine AI friendly as well.
Formal layered architecture approaches like the Clean Architecture or other Hexagonal Architecture flavors have dominated the .NET landscape as the accepted “best practices” approach for years. It was a well intentioned strategy to improve maintainability through enforcing some level of loose coupling. Today though, there’s a new factor that’s annoyingly dominating seemingly all software development discourse: the AI coding agent.
An agent doesn’t get tired, but it does have a finite context window, and it pays — in tokens, in latency, and in accuracy — for every irrelevant file it has to load to understand one feature. The structure of your codebase is now, effectively, part of the prompt. And it turns out that the architecture that’s easiest for an agent to reason about is a vertical slice approach, which is conveniently enough, what I’d prefer to do anyway and something that Wolverine is very good at.
I want to make the case that the Critter Stack — and Wolverine specifically — is the best foundation and approach in .NET for vertical slice architecture in the age of coding agents, precisely because of how aggressively it compresses the code you have to write and read for each feature as well as the structural consistency you’ll get by following our recommended idioms.
Why layered architectures fight the agent
Picture the canonical “clean” layered solution: controllers in one project, application services in another, a pile of IRequest/IRequestHandler types, repository interfaces and their implementations, DTOs, mapping profiles, and a domain project underneath it all. To change one behavior — say, how a shipment gets created — an agent has to go find the controller, the request, the handler, the validator, the repository interface, the repository implementation, and probably a mapping profile or two. They live in six or seven directories, scattered among dozens of unrelated features that share those same folders.
Every one of those files has to be pulled into the agent’s context before it can safely make a change. Most of what it loads is irrelevant to the task. The signal-to-noise ratio in the context window collapses, and that’s exactly the condition under which agents start guessing — inventing abstractions you didn’t ask for, “fixing” error cases that can’t happen, and drifting away from the intent of the change. The architecture that was supposed to manage complexity ends up manufacturing context pollution.
This isn’t a hypothetical. It’s become the dominant theme in writing about AI-ready codebases over the last year, usually under the banner of locality of reference: keep everything a feature needs in one place, and the agent loads only what’s relevant. The conclusion practitioners keep arriving at is that feature-organized, vertical-slice code is simply easier — and cheaper — for an agent to work in than layer-organized code.
Vertical slices, and the ceremony problem
Vertical slice architecture, as Jimmy Bogard originally framed it, organizes code around features instead of technical layers. A slice owns its whole pathway: take the input, do the work, produce the output, all in one place. It’s no accident that the .NET community tends to conflate VSA with MediatR — they share an originator, and MediatR’s request-per-handler model nudges you naturally toward one self-contained unit per use case.
For the record, FubuMVC (Wolverine’s predecessor) also promoted what we now call “vertical slices,” but as Jimmy told me, does it matter if nobody used the tool? (ouch).
But here’s the thing, the “just use MediatR” approach still carries a surprising amount of ceremony. For one feature you typically write a request record, the IRequest<T> marker, a handler class implementing IRequestHandler<,>, constructor injection for every dependency, an explicit SaveChangesAsync, a separate call through IMediator/IPublisher to raise follow-on events, a Program.cs registration, and a pipeline behavior to wire up validation. The slice is co-located, which is good. But it’s not small. There’s a lot of structural code surrounding the two or three lines that actually express the business decision — and the agent has to read all of it.
If fewer files and tighter context are what make vertical slices good for AI, then the natural question is: how few artifacts can a slice actually be reduced to?
Wolverine takes the slice to its logical conclusion
This is where Wolverine earns its place in the conversation. Wolverine is, in effect, vertical slice architecture compressed about as far as the language allows. Consider a typical “create a shipment” slice the MediatR way:
Look at what disappeared. No marker interfaces — Wolverine discovers handlers by convention, so the type carries no IRequest/IRequestHandler noise. No constructor and no fields — dependencies arrive by method injection, declared right where they’re used. No manual transaction management — [Transactional] (or a global auto-transaction policy) lets Wolverine and Marten manage the unit of work and use the document session as a transactional outbox. No separate publish call — returning a value is publishing it, as a cascading message. The validator is still a plain FluentValidation validator, but it’s discovered and run by Wolverine’s middleware; there’s no pipeline behavior to hand-wire.
And also, the Wolverine version also integrates a transactional outbox capability for durable execution. Now that there is so much interest in modular monoliths right now as well, I think it’s going to be important for folks to consider asynchronous workflows between modules within the same system. Conveniently enough, Wolverine has very strong support for that through its in process queueing, transactional outbox for durability, and built in Open Telemetry tracing for visibility into the asynchronous workflows. MediatR and all the tools it has inspired typically do not have any of that.
What’s left is almost entirely the business decision — and that’s the whole point!
It compresses even further on the HTTP edge. With Wolverine.Http, the endpoint is the handler — there’s no controller calling a mediator calling a handler:
And if you’re doing event sourcing with Marten, the aggregate handler workflow collapses the load-decide-append-save dance into a single method that receives the current aggregate state and returns the resulting events:
Wolverine fetches and rehydrates the Order from its event stream, hands it to you, appends whatever events you return, and commits — transactionally — without you writing any of that plumbing. The slice is the decision and nothing else.
Mediator tools were valuable when you were using them within ASP.Net Core MVC architectures where MVC controllers organized around domain entities (think InvoiceController) had a tendency to get very bloated. MediatR absolutely provided value in those days for teams to mitigate and control the accidental complexity from that type of MVC controller approach. In my opinion though, using a “mediator” should be completely unnecessary with Wolverine.
Wolverine can of course be used as just a “mediator” too, but I tend to recommend against that in most cases.
Why compression is the feature for AI
Tightly co-located slices are good for agents; small slices are better. Three reasons it compounds:
The whole slice fits in context. When a feature is one record, one validator, and one short static handler, the agent can load the entire unit of work and still have headroom for the task. It never has to reconstruct a flow from fragments strewn across layers, which is precisely the situation where agents hallucinate.
There’s far less surface to get wrong. Boilerplate isn’t free for an agent — it’s more code to generate correctly, more interfaces to implement consistently, more registration to remember. Every artifact Wolverine removes is an artifact the agent can’t fumble. Returning a cascading message can’t drift the way a hand-written IPublisher.Publish call can be forgotten or mis-ordered.
It’s cheaper to operate. This one is easy to overlook. Fewer tokens loaded per task is a direct, recurring cost reduction every single time an agent touches the code — whether that’s a developer’s Copilot session, a Claude Code run, or an automated diagnostic agent. In a world where you may be paying per token to run agents against your system, the most compressed codebase is also the cheapest one to keep an agent working in.
And by the way, our new curated AI Skills for the Critter Stack will help you settle into idiomatic vertical slice usage with Wolverine and Marten that will fit well into AI usage. And it so happens that you can purchase access to those AI Skills from the JasperFx website🙂
Compression alone isn’t the whole story
I want to be honest about where “just write tiny handlers” stops being sufficient, because the failure mode is real. When every part is small and stateless, the burden of knowing how the parts wire together — what conventions discover a handler, what a cascading return actually does, what middleware runs around your method — doesn’t vanish. It shifts somewhere. If it shifts into the agent’s context as guesswork, you’ve traded one problem for another.
The answer is conventions plus documented context. Wolverine’s behavior is convention-driven, which means it’s learnable and, more importantly, encodable. This is exactly why we ship AI skill files for the Critter Stack: they give the agent the macrostructure that the compressed slice deliberately leaves implicit — handler discovery rules, the cascading-message model, when and how the transactional middleware applies, the idiomatic shape we actually want. The skills are the constitution; the slices are the code. Together they give an agent a codebase that is both minimal to read and unambiguous to extend. Compressed code without the conventions documented is just terse code. Compressed code with the conventions encoded is an architecture an agent can work in confidently.
Two practical notes in the same honest spirit. Wolverine generates the glue code around your handlers, and that generated code is a benefit for AI — the agent writes the small handler; the framework produces the plumbing it would otherwise have to read and reproduce — but your conventions should tell the agent plainly that generated code is not to be hand-edited, and explain the model so it doesn’t fight the generator. And the classic VSA critique about duplication at scale still applies: resist the urge to grow a sprawling shared “services” layer the moment two slices rhyme. Wolverine’s middleware and compound-handler patterns are the right place to absorb genuinely shared concerns without rebuilding the layered architecture you just escaped.
It’s still in flight, but we’ve put work into our forthcoming CritterWatch tool to give you visualizations of how messages flow between systems or even between handlers within the same system. I’ll be recording a video on that some time next week.
The takeaway
The industry is converging on vertical slices as the AI-friendly way to organize code, and it’s converging there for sound reasons: locality, focus, and a clean context window. Wolverine is the most thorough expression of that idea in .NET. It strips a slice down to the business decision, removes the ceremony that an agent would otherwise have to read and reproduce, and — paired with skill files that encode the conventions — gives a coding agent a codebase that is small, coherent, and cheap to reason about.
If your architecture is now part of the prompt, the move is to make that prompt as short and as clear as it can be. That’s been the Wolverine philosophy from the start. It just happens to be exactly what the agents want too.
This is in lieu of the “official” release post I’ll make early next week after the Memorial Day holiday when I have enough rest and energy to do something much better by hand. Until then, here’s a peek at the new releases we’ll be announcing early next week — but with some published updates to our AI Skills tomorrow morning that will help you do the migrations!
The whole stack just shipped to NuGet — a coordinated major release across every Critter Stack project, built on a shared JasperFx 2.0 foundation and targeting .NET 9 + .NET 10. Headline themes: AOT-friendly, faster cold-start, and Marten ↔ Polecat dedupe onto shared infrastructure.
(Quick notes for now — a full writeup written by an actual human being with benchmarks lands next week.)
JasperFx 2.0 (foundation)
The shared base for the whole stack — also ships JasperFx.Events 2.0 + JasperFx.RuntimeCompiler 5.0.
AOT-compatible core; runtime Roslyn split into JasperFx.RuntimeCompiler so Static-mode apps drop it and the trimmer removes Roslyn.
Source generators for projection dispatch (FEC-free), CLI command discovery, and options descriptions — less runtime reflection.
Database-primitive foundation on JasperFx 2.0; the consolidation home for IStorageOperation, OperationRole, BulkInsertMode, and the SQL Server advisory lock (dedupe pillar).
Postgres / SQL Server / Sqlite / MySql / Oracle / EF Core providers, all 9.0.0.
No more runtime code generation — Roslyn is gone. Closed-shape document/event storage + Marten.SourceGenerator compiled queries. No codegen write step for Marten; AOT-publishable in Static mode.
Best-perf defaults flipped on: Quick append w/ server timestamps, advanced async tracking, bigint events, lightweight sessions, System.Text.Json. One-line revert with opts.RestoreV8Defaults().
IRevisioned.Version stays int (V8-compatible); new ILongVersioned for long. DCB gains optional HSTORE tag storage + identity-less boundary aggregates.
SQL Server / EF Core-rooted event sourcing on the shared foundation — source-generator-first and AOT-clean end to end.
Adopted the lifted JasperFx.Events async-daemon abstractions; folded SqlServerAppLock into Weasel.SqlServer.AdvisoryLock; SingleTenant lock-ids now align with Marten.
⚠️ Runtime codegen decoupled from core. Apps in the default Dynamic mode must add WolverineFx.RuntimeCompilation (dev/test), or pre-generate via codegen write + Static mode (prod). This is the #1 upgrade gotcha — see the migration guide.
⚠️ ServiceLocationPolicy.NotAllowed is the new default — restructure registrations or call opts.RestoreV5Defaults() to revert.
AOT-clean; Tier-1 cold-start static handler registry. Newtonsoft extracted to WolverineFx.Newtonsoft; IForwardsTo<T> discovery is now explicit.
Upgrading? Bump the whole stack in lockstep — JasperFx 2.0 / Weasel 9.0 / Marten 9.0 / Polecat 4.0 / Wolverine 6.0. Each migration guide above has an at-a-glance table of breaking changes. The big perf/benchmark writeup is coming next week. 🚀
Before you read any of this, just know that it’s perfectly possible to mix and match Wolverine.HTTP, MVC Core controllers, and Minimal API endpoints in the same application.
Edit: The documentation links were all wrong when I pushed this late at night of course, so:
If you’ve built ASP.NET Core applications of any size, you’ve probably run into the same friction: MVC controllers that balloon with constructor-injected dependencies, or Minimal API handlers that accumulate scattered app.MapGet(...) calls across multiple files. And if you’ve reached for a Mediator library to impose some structure, you’ve added a layer of abstraction that — while familiar — brings its own ceremony and a seam that can make unit testing harder than it should be.
Wolverine.HTTP is a different model. It’s a first-class HTTP framework built on top of ASP.NET Core that’s designed from the ground up for vertical slice architecture, has built-in transactional outbox support, and delivers a middleware story that is arguably more powerful than IEndpointFilter. And it doesn’t need a separate “Mediator” library because the Wolverine HTTP endpoints very naturally support a “Vertical Slice” style without so many moving parts as the average “check out my vertical slice architecture template!” approach online.
Moreover, Wolverine.HTTP has first class support for resilient messaging through Wolverine’s transactional outbox and asynchronous messaging. No other HTTP endpoint library in .NET has any such smooth integration.
What Is Vertical Slice Architecture?
The core idea is organizing code by feature rather than by technical layer. Instead of a Controllers/ folder, a Services/ folder, and a Repositories/ folder that all have to be navigated to understand one feature, you co-locate everything that belongs to a single use case: the request type, the handler, and any supporting types.
The payoff is locality. When a bug is filed against “create order”, you open one file. When a feature is deleted, you delete one file. There’s no hunting across layers.
Wolverine.HTTP is a natural fit for this style. A Wolverine HTTP endpoint is just a static class — no base class, no constructor injection, no framework coupling. The framework discovers it by scanning for [WolverineGet], [WolverinePost], [WolverinePut], [WolverineDelete], and [WolverinePatch] attributes.
And because of the world we live in now, I have to mention that there is already plenty of anecdotal evidence that AI assisted coding works better with the “vertical slice” approach than it does against heavily layered approaches.
Getting Started
Install the NuGet package:
dotnet add package WolverineFx.Http
Wire it up in Program.cs:
varbuilder=WebApplication.CreateBuilder(args);
builder.Host.UseWolverine();
builder.Services.AddWolverineHttp();
varapp=builder.Build();
app.MapWolverineEndpoints();
returnawaitapp.RunJasperCommands(args;
A Complete Vertical Slice
Here’s what a full feature slice looks like with Wolverine.HTTP. Request type, response type, and handler all in one place:
// The request
publicrecordCreateTodo(stringName);
// The response
publicrecordTodoCreated(intId);
// The handler — a plain static class, no base class required
publicstaticclassCreateTodoEndpoint
{
[WolverinePost("/todoitems")]
publicstaticasyncTask<IResult>Post(
CreateTodocommand,
IDocumentSessionsession) // injected by Wolverine from the IoC container
Compare that to what this would look like in MVC Core with a service layer and constructor injection. The Wolverine version is shorter, has no framework coupling in the handler method itself, and every dependency is explicit in the method signature. There’s no hidden state, and the method is trivially unit-testable in isolation.
No controller. No service interface. No repository abstraction. Just the feature.
No Separate Mediator Needed
One of the most common patterns in .NET vertical slice architecture is using a Mediator library like MediatR to dispatch commands from controllers to handlers. Wolverine makes this unnecessary — it handles both HTTP routing and in-process message dispatch with the same execution pipeline.
If you’re coming from MediatR, the key difference is that there’s no IRequest<T> base type to implement, no IRequestHandler<TRequest, TResponse> to wire up, and no _mediator.Send(command) call to thread through your controllers. The HTTP endpoint is the handler. When you also want to dispatch a message for async processing, you just return it from the method (more on that below).
Here is where Wolverine.HTTP really pulls ahead. In any event-driven architecture, HTTP endpoints frequently need to do two things atomically: save data to the database and publish a message or event. If you do these as two separate operations and something crashes between them, you’ve lost a message — or worse, written corrupted state.
The standard solution is a transactional outbox: write the message to the same database transaction as the data change, then have a background process deliver it reliably.
With plain IMessageBus in a Minimal API handler, you’re responsible for the outbox mechanics yourself. With Wolverine.HTTP, the outbox is automatic. Any message returned from an endpoint method is enrolled in the same transaction as the handler’s database work.
The simplest pattern uses tuple return values. Wolverine recognizes any message types in the return tuple and routes them through the outbox:
publicstaticclassCreateTodoEndpoint
{
[WolverinePost("/todoitems")]
publicstatic (Todotodo, TodoCreatedcreated) Post(
CreateTodocommand,
IDocumentSessionsession)
{
vartodo=newTodo { Name=command.Name };
session.Store(todo);
// Both the HTTP response (Todo) and the outbox message (TodoCreated)
// are committed in the same transaction. No message is lost.
return (todo, newTodoCreated(todo.Id));
}
}
The Todo becomes the HTTP response body. The TodoCreated message goes into the outbox and is delivered durably after the transaction commits. The database write and the message write are atomic — no coordinator needed.
If you need to publish multiple messages, use OutgoingMessages:
All four database and message operations commit together. This is the kind of correctness that is genuinely difficult to achieve with raw IMessageBus calls in Minimal API, and it comes for free in Wolverine.HTTP.
Middleware: Better Than IEndpointFilter
ASP.NET Core Minimal API introduced IEndpointFilter as its extensibility hook — a way to run logic before and after an endpoint handler. It works, but it has a few rough edges: you write a class that implements an interface with a single InvokeAsync method that receives an EndpointFilterInvocationContext, and you have to dig values out by index or type from the context object. It’s not especially readable, and composing multiple filters is verbose.
Wolverine.HTTP’s middleware model is different. Middleware is just a class with Before and After methods that can take any of the same parameters the endpoint handler can take — including the request body, IoC services, HttpContext, and even values produced by earlier middleware. Wolverine generates the glue code at compile time (via source generation), so there’s no runtime reflection and no boxing.
Here’s a stopwatch middleware that times every request:
A middleware method can also return IResult to conditionally stop the request. If the returned IResult is WolverineContinue.Result(), processing continues. Anything else — Results.Unauthorized(), Results.NotFound(), Results.Problem(...) — short-circuits the handler and writes the response immediately:
This same pattern powers Wolverine’s built-in FluentValidation middleware — every validation failure becomes a ProblemDetails response with no boilerplate in the handler itself.
The IHttpPolicy interface lets you apply middleware conventions across many endpoints at once:
Wolverine.HTTP is built on top of ASP.NET Core, not around it. Every piece of standard ASP.NET Core middleware works exactly as you’d expect — Wolverine endpoints are just routes in the middleware pipeline.
Authentication and Authorization work via the standard [Authorize] and [AllowAnonymous] attributes:
The UseRateLimiter() call in the pipeline hooks standard ASP.NET Core rate limiting middleware, and the [EnableRateLimiting] attribute wires up the policy exactly as it does for Minimal API or MVC — no Wolverine-specific configuration required.
OpenAPI / Swagger Support
Wolverine.HTTP integrates with Swashbuckle and the newer Microsoft.AspNetCore.OpenApi package. Endpoints are discovered as standard ASP.NET Core route metadata, so Swagger UI works out of the box. You can use [Tags], [ProducesResponseType], and [EndpointSummary] to enrich the generated spec:
TL;DR: Wolverine has a pretty good development and production time story for developers using EF Core and that is constantly being improved.
Wolverine was explicitly restarted 3-4 years back specifically to combine with Marten as a complete end to end solution for Event Sourcing and CQRS with asynchronous messaging support. While that “Critter Stack” strategy has definitely paid off, vastly more .NET developers and systems are using EF Core as their primary persistence mechanism. And since I’d personally like to see Wolverine get much more usage and see JasperFx Software continue to grow, we’ve made a serious effort to improve the development time experience with EF Core and Wolverine.
I should say, that’s not expressly necessary, but all of the development time accelerators, middleware, and transactional inbox/outbox integration we’re about to utilize require that library.
Let’s just get started with a simple Wolverine bootstrapping configuration that is going to use a single EF Core DbContext (for now, Wolverine happily supports using multiple DbContext types in a single application) and SQL Server for the Wolverine message persistence we’ll need for transactional outbox support later:
// Adding EF Core transactional middleware, saga support,
// and EF Core support for Wolverine storage operations
opts.UseEntityFrameworkCoreTransactions();
});
// Rest of your bootstrapping...
With that in place, let’s look at a simple message handler that uses our ItemsDbContext:
publicstaticclassCreateItemCommandHandler
{
publicstaticItemCreatedHandle(
// This would be the message
CreateItemCommandcommand,
// Any other arguments are assumed
// to be service dependencies
ItemsDbContextdb)
{
// Create a new Item entity
varitem=newItem
{
Name=command.Name
};
// Add the item to the current
// DbContext unit of work
db.Items.Add(item);
// This event being returned
// by the handler will be automatically sent
// out as a "cascading" message
returnnewItemCreated
{
Id=item.Id
};
}
}
In the handler above, you’ll notice there’s no synchronous calls at all, and that’s because we’ve turned on Wolverine’s transactional middleware for EF Core that will handle the actual transaction management. You’ll also notice that we’re using Wolverine’s cascading messages syntax to kick out an ItemCreated domain event upon the successful completion of this handler. With the EF Core transactional middleware, that is also handling any integration with Wolverine’s transactional outbox for reliable messaging. Absolutely nothing else for you to do in that handler to enable any of that behavior, and we can shove off some of the typically ugly async/await mechanics into Wolverine itself while keeping our actual application behavior cleaner.
Now let’s go a little farther and utilize some Wolverine optimizations for our EF Core usage and change the service registration up above to this:
// If you're okay with this, this will register the DbContext as normally,
// but make some Wolverine specific optimizations at the same time
That version of the integration optimizes application performance by fine tuning the service lifetimes in a way that improves Wolverine’s internal usage of the DbContext type, and adds direct mappings for Wolverine’s internal inbox and outbox storage. By using a “Wolverine optimized DbContext” like this, Wolverine is able to improve your system’s performance by allowing EF Core to batch the SQL commands for your application code and Wolverine’s transactional outbox storage in a single database round trip — and that’s important because the single most common killer of performance in enterprise applications is database chattiness!
So that’s the bare bones basics, now let’s look at some recent improvements in Wolverine for…
Development Time Usage with EF Core
We’ve invested a lot of time recently in trying to make EF Core easier to work with at development time with Wolverine. Coming from Marten where our database migrations have an “it should just work” model that quietly configures the database to match your application configuration at runtime for quick iteration at development time.
With the Wolverine.EntityFrameworkCore library, you can get that same behavior with EF Core through this option:
// This will make Wolverine do any necessary database migration
// work happen at application startup
opts.Services.AddResourceSetupOnStartup();
});
To be clear, with this setup, you can change your EF Core mappings, then restart the application or an IHost in testing and your application will automatically detect any database differences from the configuration and quietly apply a patch for you on application startup. This enables a much faster iteration cycle than EF Core Migrations do in my opinion.
The Weasel docs go deeper on the diff engine, opt-outs, and how it handles schemas.
Another feature in Marten that our community utilizes very heavily is the ability to quickly reset the state of a database in tests. I’ve also occasionally used the Respawn library for the same kind of ability when developing closer to the metal of a relational database to do the same. In a recent version of Wolverine, we’ve added similar abilities to our EF Core support including a version of Marten’s IInitialData concept to help you reset data in tests:
The ResetAllDataAsync<T>() method will look through a DbContext object to see all the tables it maps to, and delete all the data in those tables. It does take into account foreign key relationships to order its operations. After the data is wiped out, each IInitialData<T> registered in your system will be applied to lay down baseline data.
While this feature will surely have to be enhanced if many people start using it, this is already helping us make the Wolverine internal EF Core testing a lot more reliable and easier to use.
Declarative Persistence with EF Core
The next usage is special to Wolverine. A lot of times in simpler HTTP endpoints or command handlers you simply need to load an entity by its identity or primary key. And frequently, you’ll need to apply some repetitive validation that the entity exists in the first place. For that common need, Wolverine has its declarative persistence helpers like the [Entity] attribute shown below that can automatically load an entity through EF Core by its identity on the incoming command type implied by some naming conventions like this sample below:
The mapping of the identity can be explicitly mapped as well of course, and the pre-generated code always reveals Wolverine’s behavior around handlers or HTTP endpoint methods.
In the code above, Wolverine “knows” that the ItemsDbContext persists both the BacklogItems and Sprint entities, so it’s generating code around your handler to load these entities through ItemsDbContext. We can also tell Wolverine to automatically stop handling or in HTTP usage return a 400 ProblemDetails response if either of the requested entities are missing in the database. This helps keep Wolverine handler or HTTP endpoint code simpler by eliminating asynchronous code and letting you write more and more business or workflow logic in pure functions that are easy to test.
In the code above, the EF Core transactional middleware is calling ItemsDbContext.SaveChangesAsync() for you, and the automatic EF Core change tracking will catch the change to the BacklogItem.
And now, I think this is cool, Wolverine has its own new mechanism to batch up the two queries above through a custom EF Core futures query mechanism so that the handler above can fetch both the BacklogItem and the Sprint entity in one database round trip.
But wait, there’s more!
At the risk of making this blog post way too long, here’s more ways that Wolverine can make EF Core usage more successful:
I normally write this out in January, but I’m feeling like now is a good time to get this out as some of it is in flight. So with plenty of feedback from the other Critter Stack Core team members and a lot of experience seeing where JasperFx Software clients have hit friction in the past couple years, here’s my current thinking about where the Critter Stack development goes for 2026.
As I’m sure you can guess, every time I’ve written this yearly post, it’s been absurdly off the mark of what actually gets done through the year.
Critter Watch
For the love of all that’s good in this world, JasperFx Software needs to get an MVP out the door that’s usable for early adopters who are already clamoring for it. The “Critter Watch” tool, in a nutshell, should be able to tell you everything you need to know about how or why a Critter Stack application is unhealthy and then also give you the tools you need to heal your systems when anything does go wrong.
The MVP is still shaping up as:
A visualization and explanation of the configuration of your Critter Stack application
Performance metrics integration from both Marten and Wolverine
Event Store monitoring and management of projections and subscriptions
Wolverine node visualization and monitoring
Dead Letter Queue querying and management
Alerting – but I don’t have a huge amount of detail yet. I’m paying close attention to the issues JasperFx clients see in production applications though, and using that to inform what information Critter Watch will surface through its user interface and push notifications
This work is heavily in flight, and will hopefully accelerate over the holidays and January as JasperFx Software clients tend to be much quieter. I will be publishing a separate vision document soon for users to review.
The Entire “Critter Stack”
We’re standing up the new docs.jasperfx.net (Babu is already working on this) to hold documentation on supporting libraries and more tutorials and sample projects that cross Marten & Wolverine. This will finally add some documentation for Weasel (database utilities and migration support), our command line support, the stateful resource model, the code generation model, and everything to do with DevOps recipes.
Play the “Cold Start Optimization” epic across both Marten and Wolverine (and possibly Lamar). I don’t think that true AOT support is feasible, but maybe we can get a lot closer. Have an optimized start mode of some sort that eliminates all or at least most of:
Reflection usage in bootstrapping
Reflection usage at runtime, which today is really just occasional calls to object.GetType()
Assembly scanning of any kind, which we know can be very expensive for some systems with very large dependency trees.
Increased and improved integration with EF Core across the stack
Marten
The biggest set of complaints I’m hearing lately is all around views between multiple entity types or projections involving multiple stream types or multiple entity types. I also got some feedback from multiple past clients about the limitation of Marten as a data source underneath UI grids, which isn’t particularly a new bit of feedback. In general, there also appears to be a massive opportunity to improve Marten’s usability for many users by having more robust support in the box for projecting event data to flat, denormalized tables.
I think I’d like to prioritize a series of work in 2026 to alleviate the complicated view problem:
The “Composite Projections” Epic where you might use the build products of upstream projections to create multi-stream projection views. This is also an opportunity to ratchet up even more scalability and throughput in the daemon. I’ve gotten positive feedback from a couple JasperFx clients about this. It’s also a big opportunity to increase the throughput and scalability of the Async Daemon by making fewer database requests
Revisit GroupJoin in the LINQ support even though that’s going to be absolutely miserable to build. GroupJoin() might end up being a much easier usage that all our Include() functionality.
A first class model to project Marten event data with EF Core. In this proposed model, you’d use an EF Core DbContext to do all the actual writes to a database.
Other than that, some other ideas that have kicked around for awhile are:
Improve the documentation and sample projects, especially around the usage of projections
Take a better look at the full text search features in Marten
Finally support the PostGIS extension in Marten. I think that could be something flashy and quick to build, but I’d strongly prefer to do this in the context of an actual client use case.
Continue to improve our story around multi-stream operations. I’m not enthusiastic about “Dynamic Boundary Consistency” (DCB) in regards to Marten though, so I’m not sure what this actually means yet. This might end up centering much more on the integration with Wolverine’s “aggregate handler workflow” which is already perfectly happy to support strong consistency models even with operations that touch more than one event stream.
Wolverine
Wolverine is by far and away the busiest part of the Critter Stack in terms of active development right now, but I think that slows down soon. To be honest, most work at this point is us reacting tactically to JasperFx client or user needs. In terms of general, strategic themes, I think that 2026 will involve:
In conjunction with “CritterWatch”, improving Wolverine’s management story around dead letter queueing
I would love to expand Wolverine’s database support beyond “just” SQL Server and PostgreSQL
Improving the Kafka integration. That’s not our most widely used messaging broker, but that seems to be the leading source of enhancement requests right now
New Critters?
We’ve done a lot of preliminary work to potentially build new Critter Stack event store alternatives based on different database engines. I’ve always believed that SQL Server would be the logical next database engine, but we’ve gotten fewer and fewer requests for this as PostgreSQL has become a much more popular database choice in the .NET ecosystem.
I’m not sure this will be a high priority in 2026, but you never know…
A JasperFx Software client was asking recently about the features for software controlled load balancing and “sticky” agents I’m describing in this post. Since these features are both critical for Wolverine functionality and maybe not perfectly documented already, it’s a great topic for a new blog post!Both because it’s helpful to understand what’s going on under the covers if you’re running Wolverine in production and also in case you want to build your own software managed load distribution for your own virtual agents.
Wolverine was rebooted around in 2022 as a complement to Marten to extend the newly named “Critter Stack” into a full Event Driven Architecture platform and arguably the only single “batteries included” technical stack for Event Sourcing on the .NET platform.
One of the things that Wolverine does for Marten is to provide a first class event subscription function where Wolverine can either asynchronously process events captured by Marten in strict order or forward those events to external messaging brokers. Those first class event subscriptions and the existing asynchronous projection support from Marten can both be executed in only one process at a time because the processing is stateful. As you can probably imagine, it would be very helpful for your system’s scalability and performance if those asynchronous projections and subscriptions could be spread out over an executing cluster of system nodes.
Fortunately enough, Wolverine works with Marten to provide its subscription and projection distribution to assign different asynchronous projections and event subscriptions to run on different nodes so you have a bit more even spread of work throughout your running application cluster like this illustration:
To support that capability above, Wolverine uses a combination of its Leader Election that allows Wolverine to designate one — and only one — node within an application cluster as the “leader” and it’s “agent family” feature that allows for assigning stateful agents across a running cluster of nodes. In the case above, there’s a single agent for every configured projection or subscription in the application that Wolverine will try to spread out over the application cluster.
Just for the sake of completeness, if you have configured Marten for multi-tenancy through separate databases, Wolverine’s projection/subscription distribution will distribute by database rather than by individual projection or subscription + database.
Alright, so here’s the things you might want to know about the subsystem above:
You need to have some sort of Wolverine message persistence configured for your application. You might already be familiar with that for the transactional inbox or outbox storage, but there’s also storage to persist information about the running nodes and agents within your system that’s important for both the leader election and agent assignments
There has to be some sort of “control endpoint” configured for Wolverine to be able to communicate between specific nodes. There is a built in “database control” transport that can act as a fallback mechanism, but all of this back and forth communication works better with transports like Wolverine’s Rabbit MQ integration that can quietly use non-durable queues per node for this intra-node communication. And in case you’re wondering, the Rabbit MQ
Wolverine’s leader election process tries to make sure that there is always a single node that is running the “leader agent” that is monitoring the other running node status and all the known agents
Wolverine’s agent (some other frameworks call these “virtual actors“) subsystem consisting of the IAgentFamily and IAgent interfaces
Building Your Own Agents
Let’s say you have some kind of stateful process in your system that you want to always be running like something that polls against an external system maybe. And then because this is a somewhat common scenario, let’s say that you need a completely separate polling mechanism against different outside entities or tenants.
First, we need to implement this Wolverine interface to be able to start and stop agents in your application:
/// <summary>
/// Models a constantly running background process within a Wolverine
/// node cluster
/// </summary>
public interface IAgent : IHostedService
{
/// <summary>
/// Unique identification for this agent within the Wolverine system
/// </summary>
Uri Uri { get; }
/// <summary>
/// Is the agent running, stopped, or paused? Not really used
/// by Wolverine *yet*
/// </summary>
AgentStatus Status { get; }
}
IHostedService up above is the same old interface from .NET for long running processes, and Wolverine just adds a Uri and currently unused Status property (that hopefully gets used by “CritterWatch” someday soon for health checks). You could even use the BackgroundService from .NET itself as a base class.
Next, you need a way to tell Wolverine what agents exist and a strategy for distributing the agents across a running application cluster by implementing this interface:
/// <summary>
/// Pluggable model for managing the assignment and execution of stateful, "sticky"
/// background agents on the various nodes of a running Wolverine cluster
/// </summary>
public interface IAgentFamily
{
/// <summary>
/// Uri scheme for this family of agents
/// </summary>
string Scheme { get; }
/// <summary>
/// List of all the possible agents by their identity for this family of agents
/// </summary>
/// <returns></returns>
ValueTask<IReadOnlyList<Uri>> AllKnownAgentsAsync();
/// <summary>
/// Create or resolve the agent for this family
/// </summary>
/// <param name="uri"></param>
/// <param name="wolverineRuntime"></param>
/// <returns></returns>
ValueTask<IAgent> BuildAgentAsync(Uri uri, IWolverineRuntime wolverineRuntime);
/// <summary>
/// All supported agent uris by this node instance
/// </summary>
/// <returns></returns>
ValueTask<IReadOnlyList<Uri>> SupportedAgentsAsync();
/// <summary>
/// Assign agents to the currently running nodes when new nodes are detected or existing
/// nodes are deactivated
/// </summary>
/// <param name="assignments"></param>
/// <returns></returns>
ValueTask EvaluateAssignmentsAsync(AssignmentGrid assignments);
}
In this case, you can plug custom IAgentFamily strategies into Wolverine by just registering a concrete service in your DI container against that IAgentFamily interface. Wolverine does a simple IServiceProvider.GetServices<IAgentFamily>() during its boostrapping to find them.
As you can probably guess, the Scheme should be unique, and the Uri structure needs to be unique across all of your agents. EvaluateAssignmentsAsync() is your hook to create distribution strategies, with a simple “just distribute these things evenly across my cluster” strategy possible like this example from Wolverine itself:
public ValueTask EvaluateAssignmentsAsync(AssignmentGrid assignments)
{
assignments.DistributeEvenly(Scheme);
return ValueTask.CompletedTask;
}
If you go looking for it, the equivalent in Wolverine’s distribution of Marten projections and subscriptions is a tiny bit more complicated in that it uses knowledge of node capabilities to support blue/green semantics to only distribute work to the servers that “know” how to use particular agents (like version 3 of a projection that doesn’t exist on “blue” nodes):
public ValueTask EvaluateAssignmentsAsync(AssignmentGrid assignments)
{
assignments.DistributeEvenlyWithBlueGreenSemantics(SchemeName);
return new ValueTask();
}
The AssignmentGrid tells you the current state of your application in terms of which node is the leader, what all the currently running nodes are, and which agents are running on which nodes. Beyond the even distribution, the AssignmentGrid has fine grained API methods to start, stop, or reassign agents.
To wrap this up, I’m trying to guess at the questions you might have and see if I can cover all the bases:
Is some kind of persistence necessary? Yes, absolutely. Wolverine has to have some way to “know” what nodes are running and which agents are really running on each node.
How does Wolverine do health checks for each node? If you look in the wolverine_nodes table when using PostgreSQL or Sql Server, you’ll see a heartbeat column with a timestamp. Each Wolverine application is running a polling operation that updates its heartbeat timestamp and also checks that there is a known leader node. In normal shutdown, Wolverine tries to gracefully mark the current node as offline and send a message to the current leader node if there is one telling the leader that the node is shutting down. In real world usage though, Kubernetes or who knows what is frequently killing processes without a clean shutdown. In that case, the leader node will be able to detect stale nodes that are offline, eject them from the node persistence, and redistribute agents.
Can Wolverine switch over the leadership role? Yes, and that should be relatively quick. Plus Wolverine would keep trying to start a leader election if none is found. But yet, it’s an imperfect world where things can go wrong and there will 100% be the ability to either kickstart or assign the leader role from the forthcoming CritterWatch user interface.
How does the leadership election work? Crudely and relatively effectively. All of the storage mechanics today have some kind of sequential node number assignment for all newly persisted nodes. In a kind of simplified “Bully Algorithm,” Wolverine will always try to send “try assume leadership” messages to the node with the lowest sequential node number which will always be the longest running node. When a node does try to take leadership, it uses whatever kind of global, advisory lock function the current persistence uses to get sole access to write the leader node assignment to itself, but will back out if the current node detects from storage that the leadership is already running on another active node.
Can I extract the Wolverine leadership election for my own usage? Not easily at all, sorry. I don’t have the link anywhere handy, but there is I believe a couple OSS libraries in .NET that implement the Raft consensus algorithm for leader election. I honestly don’t remember why I didn’t think that was suitable for Wolverine though. Leadership election is most certainly not something for the feint of heart.
Summary
I’m not sure how useful this post was for most users, but hopefully it’s helpful to some. I’m sure I didn’t hit every possible question or concern you might have, so feel free to reach out in Discord or comments here with any questions.
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.”
Railway Programming is an idea that came out of the F# community as a way to develop for “sad path” exception cases without having to resort to throwing .NET Exceptions as a way of doing flow control. Railway Programming works by chaining together functions with a standardized response in such a way that it’s relatively easy to abort workflows as preliminary steps are found to be invalid while still passing the results of the preceding function as the input into the next function.
Wolverine has some direct support for a quasi-Railway Programming approach by moving validation or data loading steps prior to the main message handler or HTTP endpoint logic. Let’s jump into a quick sample that works with either message handlers or HTTP endpoints using the built in HandlerContinuation enum:
public static class ShipOrderHandler
{
// This would be called first
public static async Task<(HandlerContinuation, Order?, Customer?)> LoadAsync(ShipOrder command, IDocumentSession session)
{
var order = await session.LoadAsync<Order>(command.OrderId);
if (order == null)
{
return (HandlerContinuation.Stop, null, null);
}
var customer = await session.LoadAsync<Customer>(command.CustomerId);
return (HandlerContinuation.Continue, order, customer);
}
// The main method becomes the "happy path", which also helps simplify it
public static IEnumerable<object> Handle(ShipOrder command, Order order, Customer customer)
{
// use the command data, plus the related Order & Customer data to
// "decide" what action to take next
yield return new MailOvernight(order.Id);
}
}
By naming convention (but you can override the method naming with attributes as you see fit), Wolverine will try to generate code that will call methods named Before/Validate/Load(Async) before the main message handler method or the HTTP endpoint method. You can use this compound handler approach to do set up work like loading data required by business logic in the main method or in this case, as validation logic that can stop further processing based on failed validation or data requirements or system state. Some Wolverine users like using these method to keep the main methods relatively simple and focused on the “happy path” and business logic in pure functions that are easier to unit test in isolation.
By returning a HandlerContinuation value either by itself or as part of a tuple returned by a Before, Validate, or LoadAsync method, you can direct Wolverine to stop all other processing.
You have more specialized ways of doing that in HTTP endpoints by using the ProblemDetails specification to stop processing like this example that uses a Validate() method to potentially stop processing with a descriptive 400 and error message:
public record CategoriseIncident(
IncidentCategory Category,
Guid CategorisedBy,
int Version
);
public static class CategoriseIncidentEndpoint
{
// This is Wolverine's form of "Railway Programming"
// Wolverine will execute this before the main endpoint,
// and stop all processing if the ProblemDetails is *not*
// "NoProblems"
public static ProblemDetails Validate(Incident incident)
{
return incident.Status == IncidentStatus.Closed
? new ProblemDetails { Detail = "Incident is already closed" }
// All good, keep going!
: WolverineContinue.NoProblems;
}
// 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);
}
}
The value WolverineContinue.NoProblems tells Wolverine that everything is good, full speed ahead. Anything else will write the ProblemDetails value out to the response, return a 400 status code (or whatever you decide to use), and stop processing. Returning a ProblemDetails object hopefully makes these filter methods easy to unit test themselves.
You can also use the AspNetCore IResult as another formally supported “result” type in these filter methods like this shown below:
public static class ExamineFirstHandler
{
public static bool DidContinue { get; set; }
public static IResult Before([Entity] Todo2 todo)
{
return todo != null ? WolverineContinue.Result() : Results.Empty;
}
[WolverinePost("/api/todo/examinefirst")]
public static void Handle(ExamineFirst command) => DidContinue = true;
}
In this case, the “special” value WolverineContinue.Result() tells Wolverine to keep going, otherwise, Wolverine will execute the IResult returned from one of these filter methods and stop all other processing for the HTTP request.
It’s maybe a shameful approach for folks who are more inline with a Functional Programming philosophy, but you could also use a signature like:
[WolverineBefore]
public static UnauthorizedHttpResult? Authorize(SomeCommand command, ClaimsPrincipal user)
In the case above, Wolverine will do nothing if the return value is null, but will execute the UnauthorizedHttpResult response if there is, and stop any further processing. There is *some* minor value to expressing the actual IResult type above because that can be used to help generate OpenAPI metadata.
Lastly, let’s think about the very common need to write an HTTP endpoint where you want to return a 404 status code if the requested data doesn’t exist. In many cases the API user is supplying the identity value for an entity, and your HTTP endpoint will first query for that data, and if it doesn’t exist, abort the processing with the 404 status code. Wolverine has some built in help for this tedious task through its unique persistence helpers as shown in this sample HTTP endpoint below:
[WolverineGet("/orders/{id}")]
public static Order GetOrder([Entity] Order order) => order;
Note the presence of the [Entity] attribute for the Order argument to this HTTP endpoint route. That’s telling Wolverine that that data should be loaded using the “id” route argument as the Order key from whatever persistence mechanism in your application deals with the Order entity, which could be Marten of course, an EF Core DbContext that has a mapping for Order, or Wolverine’s RavenDb integration. Unless we purposely mark [Entity(Required = false)], Wolverine.HTTP will return a 404 status code if the Order entity does not exist. The simplistic sample from Wolverine’s test suite above doesn’t do any kind of mapping from the raw Order to a view model, but the mechanics of the [Entity] loading would work equally if you also mapped the raw Order to some kind of OrderViewModel maybe.
Last Thoughts
I’m pushing Wolverine users and JasperFx clients to utilize Wolverine’s quasi Railway Programming capabilities as guard clauses to better separate out validation or error condition handling into easily spotted, atomic operations while reducing the core HTTP request or message handler to being a “happy path” operation. Especially in HTTP services where the ProblemDetails specification and integration with Wolverine fits well with this pattern and where I’d expect many HTTP client tools to already know how to work with problem details responses.
There have been a few attempts to adapt Railway Programming to C# that I’m aware of, inevitably using some kind of custom Result type that denotes success or failure with the actual results for the next function. I’ve seen some folks and OSS tools try to chain functions together with nested lambda functions within a fluent interface. I’m not a fan of any of this because I think the custom Result types just add code noise and extra mechanical work, then the fluent Interface approach can easily be nasty to debug and detracts from readability by the extra code noise. But anyway, read a lot more about this in Andrew Lock’s Series: Working with the result pattern and make up your own mind.
I’ve also seen an approach where folks used MediatR handlers for each individual step in the “railway” where each handler had to return a custom Result type with the inputs for the next handler in the series. I beg you, please don’t do this in your own system because that leads to way too much complexity, code that’s much harder to reason about because of the extra hoops and indirection, and potentially poor system performance because again, you can’t see what the code is doing and you can easily end up making unnecessarily duplicate database round trips or just being way too “chatty” to the database. And no, replacing MediatR handlers with Wolverine handlers is not going to help because the pattern was the problem and not MediatR itself.
As always, the Wolverine philosophy is that the path to long term success in enterprise-y software systems is by relentlessly eliminating code ceremony so that developers can better reason about how the system’s logic and behavior works. To a large degree, Wolverine is a reaction to the very high ceremony Clean/Onion Architecture/iDesign architectural approaches of the past 15-20 years and how hard those systems can be to deal with over time.
And as happens with just about any halfway good thing in programming, some folks overused the Railway Programming idea and there’s a little bit of pushback or backlash to the technique. I can’t find the quote to give it the real attribution, but something I’ve heard Martin Fowler say is that “we don’t know how useful an idea really can be until we push it too far, then pull back a little bit.”