Skip to content

QUERY verb docs: a QUERY endpoint IS wrapped in transactional middleware when it takes an IDocumentSession/DbContext (follow-up to #3296) #3355

Description

@jeremydmiller

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions