Add a declarative way to say "run this after the transactional commit" — a convention or attribute that appends to Postprocessors rather than inserting at the front.
The problem
After reads like a post-handler hook that runs at the end. It does not run after the commit. Chain.cs:360 and MiddlewarePolicy.cs:94 both do:
chain.Postprocessors.Insert(i, frame);
— front of the list. The transactional commit is itself a postprocessor (DocumentSessionSaveChanges, added by each store's PersistenceFrameProvider.ApplyTransactionSupport), so an After method runs before the write is durable.
That is a reasonable default for "after the handler," but it means there is currently no supported way to express "after the commit" — even though the position exists and Wolverine itself uses it: ApplyTransactionSupport does
chain.Postprocessors.Add(new DocumentSessionSaveChanges());
chain.Postprocessors.Add(new FlushOutgoingMessages());
so FlushOutgoingMessages is a first-party post-commit postprocessor.
What applications do instead, and why it's fragile
They hand-write a Frame and Add it from a policy sequenced after the persistence policy. That works — but the ordering is positional and unstated. Nothing in the source declares the intent, nothing validates it, and a change in policy application order silently moves the frame to the wrong side of the commit with every test still green.
The failure mode is the bad one: side effects that are supposed to follow a durable write instead run before it, and only diverge when a commit fails.
Where this has cost real time
Two cases in CritterWatch, both of which reached for After first because the name implies it:
- An alert raise/resolve latch that had to be set only after the append committed. Using
After recorded "alert raised" for a transaction that could still fail. The workaround was a hand-written early SaveChangesAsync inside the handler.
- An ingest cache write-back that must not publish state for a batch that was never persisted — same shape, same workaround. Its code comment calls the resulting explicit save "the most load-bearing one in the codebase."
In both cases the correct mechanism (a frame appended after the commit) existed but had to be discovered by reading Wolverine's own frame-provider source.
Ask
Something like:
public static class SomeHandler
{
public static void Handle(...) { }
[WolverineAfterCommit] // or: an `AfterCommit` conventional method name
public static void AfterCommit(...) { }
}
that appends to Postprocessors after the persistence provider's commit frame, with the same variable-binding rules After already has.
Two properties matter more than the spelling:
- It must land after the commit frame regardless of policy ordering — that is the whole point; getting it by luck of sequencing is what applications already do.
- It must not run when the commit throws. Frames are concatenated without a
try/finally (HandlerChain.cs:709-717), so unwinding already gives this — worth pinning with a test so it stays true.
Nice-to-have
If After's pre-commit position is kept (and it probably should be, for compatibility), consider mentioning the distinction in the middleware docs. The name is doing a lot of work and currently points the wrong way for anyone thinking about durability.
Add a declarative way to say "run this after the transactional commit" — a convention or attribute that appends to
Postprocessorsrather than inserting at the front.The problem
Afterreads like a post-handler hook that runs at the end. It does not run after the commit.Chain.cs:360andMiddlewarePolicy.cs:94both do:— front of the list. The transactional commit is itself a postprocessor (
DocumentSessionSaveChanges, added by each store'sPersistenceFrameProvider.ApplyTransactionSupport), so anAftermethod runs before the write is durable.That is a reasonable default for "after the handler," but it means there is currently no supported way to express "after the commit" — even though the position exists and Wolverine itself uses it:
ApplyTransactionSupportdoesso
FlushOutgoingMessagesis a first-party post-commit postprocessor.What applications do instead, and why it's fragile
They hand-write a
FrameandAddit from a policy sequenced after the persistence policy. That works — but the ordering is positional and unstated. Nothing in the source declares the intent, nothing validates it, and a change in policy application order silently moves the frame to the wrong side of the commit with every test still green.The failure mode is the bad one: side effects that are supposed to follow a durable write instead run before it, and only diverge when a commit fails.
Where this has cost real time
Two cases in CritterWatch, both of which reached for
Afterfirst because the name implies it:Afterrecorded "alert raised" for a transaction that could still fail. The workaround was a hand-written earlySaveChangesAsyncinside the handler.In both cases the correct mechanism (a frame appended after the commit) existed but had to be discovered by reading Wolverine's own frame-provider source.
Ask
Something like:
that appends to
Postprocessorsafter the persistence provider's commit frame, with the same variable-binding rulesAfteralready has.Two properties matter more than the spelling:
try/finally(HandlerChain.cs:709-717), so unwinding already gives this — worth pinning with a test so it stays true.Nice-to-have
If
After's pre-commit position is kept (and it probably should be, for compatibility), consider mentioning the distinction in the middleware docs. The name is doing a lot of work and currently points the wrong way for anyone thinking about durability.