
A deep read of Jeremy Miller's August 13 case study on using Wolverine's returned side-effect values to keep C# business endpoints focused, testable, and explicit about execution boundaries.
await needs a new abstraction.AddTenantRequest, an IDocumentSession, an IMessageBus, an AuditLogService, and an HttpContext. The omitted body appends an event, publishes a command, and writes an audit record. The endpoint therefore mixes the operation's business inputs with the services needed to carry it out. The original code and explanation are in Miller's post.AuditLogService to record what happened, and it needs HttpContext only to reach httpContext.User, which supplies a ClaimsPrincipal for the audit actor. The endpoint does not otherwise inspect the request context. Miller's point is not that dependency injection is wrong. His point is that the signature now carries a large object and an infrastructure service because one small piece of post-operation bookkeeping happens to live there.ISideEffect marker interface has no method of its own. A handler or HTTP endpoint returns an object that implements the marker, and Wolverine looks for a public Execute() or ExecuteAsync() method on that object after the handler returns. Wolverine resolves the parameters of that execution method as dependencies in the generated chain. The source post shows the interface and the execution convention.public record AuditLog(
string Action,
string ServiceName,
string? TargetUri = null,
string? Details = null,
Dictionary<string, string>? Parameters = null,
string? InitiatedBy = null) : ISideEffect
{
public Task ExecuteAsync(AuditLogService auditLog, ClaimsPrincipal? user)
{
var actor = string.IsNullOrWhiteSpace(InitiatedBy)
? AuditActor.From(user)
: InitiatedBy;
return auditLog.LogAsync(
Action, ServiceName, TargetUri, Details, Parameters, actor);
}
}[WolverinePost("/test/audited/{serviceName}")]
public static AuditLog Post(string serviceName)
=> new("TestAction", serviceName, Details: "smoke");AuditLogService, ClaimsPrincipal, or HttpContext. Wolverine resolves AuditLogService and ClaimsPrincipal when it executes the returned value, while the endpoint only constructs the description of the audit event. The result is not a magical disappearance of side effects. The effect still runs. The design changes which function owns the description and which layer owns execution.serviceName and request describe the operation. IDocumentSession and IMessageBus support the omitted event and command work. AuditLogService and HttpContext exist for the audit record. Miller says that two parameters are present for the audit log alone, and that the request context exists for one expression, httpContext.User.AuditLog value. It should not need to start a store, bus, audit service, and request context merely to discover what the endpoint intended to record. The returned record creates that inspection point without claiming that persistence itself is pure.ISideEffect is a marker, not an effect interpreterpublic interface ISideEffect
: IWolverineReturnType, INotToBeRouted;Execute method, a result type, or a generic effect algebra. Wolverine treats it as a return-type signal and then follows a convention on the concrete value. That choice keeps the application code compact, but it also means the behavior depends on Wolverine's runtime and code-generation rules. The pattern is therefore framework-directed functional design, not a general-purpose effect system that can be moved unchanged to another host language.AuditLog.ExecuteAsync asks for AuditLogService and ClaimsPrincipal?, so Wolverine resolves those dependencies at execution time. The endpoint does not thread them through a signature that never uses them for its own decision.
AuditLog value, Wolverine resolves the execution dependencies, and ExecuteAsync performs the audit write after the endpoint returns. The diagram visualizes the mechanism in Miller's post; it is not an image from the source post.AuditLog record carries the action, service name, optional target URI, details, parameters, and an optional initiating actor. Its ExecuteAsync method supplies a fallback actor from the resolved ClaimsPrincipal when InitiatedBy is empty. The value therefore contains the stable facts of the event, while execution supplies the service and request-derived context at the boundary where the effect actually runs. Miller's code defines these fields and this fallback.AuditLog data. A separate integration test can check that Wolverine invokes ExecuteAsync and that the audit service persists the result. The source article does not claim that one test category replaces the other. It shows how to keep the business decision from requiring the second category just to inspect the first.ISideEffect. He gives two limits:await has no distracting dependencies and nobody needs to test the operation in isolation.await.ISideEffect name. It is the separation between deciding that an effect should happen and performing an effect whose result is irrelevant to the decision. Many functional systems express that separation with an effect value, an instruction, a command, an event, or a free-monad-like program. Wolverine expresses it with a C# record plus a framework convention.await may be clearer than a marker interface and a convention. Repeated dependency plumbing across several entry points is stronger evidence that the effect deserves its own value."TheHttpContextparameter didn't move somewhere else — it stopped existing."Jeremy Miller, "Using the Wolverine 'Side Effect' Model to Simplify Code", August 13, 2026.
"Pure functions are great for testability"Jeremy Miller, the same post, summarizing the practical motivation for the refactoring.
"That ratio is the tell."Jeremy Miller, the same post, referring to repeated dependency plumbing around a small post-operation fact.
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.