Follow-up to #3296 (QUERY verb support, shipped in 6.17.0).
Summary
WolverineQueryAttribute's XML doc, the sample_wolverine_query_endpoint comment, and — by omission — the query_endpoint_is_not_wrapped_in_transactional_middleware test together imply that a QUERY endpoint is exempt from transactional middleware because QUERY is a safe verb. It isn't. Nothing in Wolverine keys off the HTTP verb. A QUERY endpoint that takes an IDocumentSession is wrapped in transactional middleware under AutoApplyTransactions(), exactly as a GET endpoint with the same dependency already is today.
The parenthetical in the XML doc — "the same dependency-based rule as every other verb" — is correct. The claim it qualifies is not, because it names the wrong dependency.
What the XML doc says
src/Http/Wolverine.Http/ModifyHttpChainAttribute.cs:
Because it is safe, a QUERY endpoint is not wrapped in transactional/outbox middleware unless it takes a message-bus dependency (the same dependency-based rule as every other verb); unlike GET, it is allowed to bind a request body.
This conflates two independent rules:
- Outbox middleware is gated on an
IMessageBus / MessageContext dependency — HttpChain.RequiresOutbox(). The doc is right about this one.
- Transactional middleware is gated on the persistence provider's
CanApply, not on the message bus. MartenPersistenceFrameProvider.CanApply returns true for an IDocumentSession or IDocumentOperations dependency (notably not IQuerySession). EFCorePersistenceFrameProvider.CanApply returns true for any DbContext dependency. AutoApplyTransactions has no verb guard.
So "no message-bus dependency" does not imply "not transactional."
Measured behavior
Probe against the WolverineWebApi host (which enables AutoApplyTransactions(), EF Core and Marten), reading HttpChain.IsTransactional / RequiresOutbox():
GET /data/{id} (IDocumentSession): IsTransactional=True RequiresOutbox=False
QUERY /search (no deps): IsTransactional=False RequiresOutbox=False
QUERY /search-tx (IDocumentSession): IsTransactional=True RequiresOutbox=False
QUERY /search-querysession (IQuerySession): IsTransactional=False RequiresOutbox=False
/search-tx and /search-querysession were added to QueryEndpoints.cs for the probe and reverted; GET /data/{id} is the existing ServiceEndpoints.GetData.
A QUERY endpoint taking an IDocumentSession gets a SaveChangesAsync postprocessor on what is supposed to be a safe, read-only request. With EF Core it is worse: there is no read-only DbContext abstraction, so the natural Search(SearchRequest, AppDbContext) shape attracts the transactional middleware (and, in Eager mode, a real BeginTransactionAsync) unless the author remembers [NonTransactional].
Why the existing test doesn't catch it
query_endpoint_is_not_wrapped_in_transactional_middleware asserts on /search, whose handler is:
[WolverineQuery("/search")]
public static SearchResults Search(SearchRequest request)
No persistence dependency at all. IsTransactional is trivially false — a [WolverinePost] with that signature would assert identically. The test demonstrates "an endpoint with no dependencies is not transactional," not "QUERY is not transactional."
Suggested changes
WolverineQueryAttribute XML doc — this is what users see in IntelliSense, so it matters most. Something like: QUERY is safe and idempotent like GET, but binds a request body. Wolverine applies no verb-specific middleware rules: outbox middleware still requires an IMessageBus/IMessageContext dependency, and transactional middleware still applies if the handler takes an IDocumentSession or DbContext. Prefer IQuerySession (Marten) or [NonTransactional] (EF Core) to keep a query endpoint free of transactional middleware.
sample_wolverine_query_endpoint comment in QueryEndpoints.cs — "because the endpoint takes no message-bus dependency — no transactional/outbox middleware is applied" is accidentally true here (the endpoint takes no dependency of any kind) but teaches the wrong rule. Suggest attributing it to the absence of a session/DbContext dependency, or injecting IQuerySession to make the realistic case explicit.
- Strengthen the test — add a QUERY endpoint with an
IDocumentSession and one with an IQuerySession, and assert IsTransactional is True and False respectively. That pins the actual rule and guards against a future verb-based special case being introduced silently.
docs/guide/http/endpoints.md — "The HTTP QUERY Method" section carries the same framing and should get the same correction.
Happy to open a PR for any or all of the above if useful.
Context
Found while writing AI-skill coverage for the QUERY verb (JasperFx/ai-skills#84). The skills now document the dependency-based rule as measured above; this issue is to bring Wolverine's own docs and the attribute XML doc in line.
Follow-up to #3296 (QUERY verb support, shipped in 6.17.0).
Summary
WolverineQueryAttribute's XML doc, thesample_wolverine_query_endpointcomment, and — by omission — thequery_endpoint_is_not_wrapped_in_transactional_middlewaretest together imply that a QUERY endpoint is exempt from transactional middleware because QUERY is a safe verb. It isn't. Nothing in Wolverine keys off the HTTP verb. A QUERY endpoint that takes anIDocumentSessionis wrapped in transactional middleware underAutoApplyTransactions(), exactly as a GET endpoint with the same dependency already is today.The parenthetical in the XML doc — "the same dependency-based rule as every other verb" — is correct. The claim it qualifies is not, because it names the wrong dependency.
What the XML doc says
src/Http/Wolverine.Http/ModifyHttpChainAttribute.cs:This conflates two independent rules:
IMessageBus/MessageContextdependency —HttpChain.RequiresOutbox(). The doc is right about this one.CanApply, not on the message bus.MartenPersistenceFrameProvider.CanApplyreturns true for anIDocumentSessionorIDocumentOperationsdependency (notably notIQuerySession).EFCorePersistenceFrameProvider.CanApplyreturns true for anyDbContextdependency.AutoApplyTransactionshas no verb guard.So "no message-bus dependency" does not imply "not transactional."
Measured behavior
Probe against the
WolverineWebApihost (which enablesAutoApplyTransactions(), EF Core and Marten), readingHttpChain.IsTransactional/RequiresOutbox():/search-txand/search-querysessionwere added toQueryEndpoints.csfor the probe and reverted;GET /data/{id}is the existingServiceEndpoints.GetData.A QUERY endpoint taking an
IDocumentSessiongets aSaveChangesAsyncpostprocessor on what is supposed to be a safe, read-only request. With EF Core it is worse: there is no read-onlyDbContextabstraction, so the naturalSearch(SearchRequest, AppDbContext)shape attracts the transactional middleware (and, inEagermode, a realBeginTransactionAsync) unless the author remembers[NonTransactional].Why the existing test doesn't catch it
query_endpoint_is_not_wrapped_in_transactional_middlewareasserts on/search, whose handler is:No persistence dependency at all.
IsTransactionalis trivially false — a[WolverinePost]with that signature would assert identically. The test demonstrates "an endpoint with no dependencies is not transactional," not "QUERY is not transactional."Suggested changes
WolverineQueryAttributeXML doc — this is what users see in IntelliSense, so it matters most. Something like: QUERY is safe and idempotent like GET, but binds a request body. Wolverine applies no verb-specific middleware rules: outbox middleware still requires anIMessageBus/IMessageContextdependency, and transactional middleware still applies if the handler takes anIDocumentSessionorDbContext. PreferIQuerySession(Marten) or[NonTransactional](EF Core) to keep a query endpoint free of transactional middleware.sample_wolverine_query_endpointcomment inQueryEndpoints.cs— "because the endpoint takes no message-bus dependency — no transactional/outbox middleware is applied" is accidentally true here (the endpoint takes no dependency of any kind) but teaches the wrong rule. Suggest attributing it to the absence of a session/DbContext dependency, or injectingIQuerySessionto make the realistic case explicit.IDocumentSessionand one with anIQuerySession, and assertIsTransactionalisTrueandFalserespectively. That pins the actual rule and guards against a future verb-based special case being introduced silently.docs/guide/http/endpoints.md— "The HTTP QUERY Method" section carries the same framing and should get the same correction.Happy to open a PR for any or all of the above if useful.
Context
Found while writing AI-skill coverage for the QUERY verb (JasperFx/ai-skills#84). The skills now document the dependency-based rule as measured above; this issue is to bring Wolverine's own docs and the attribute XML doc in line.