Queries
A query is an immutable, validated read intent -- a named request for data on the read side of CQRS. Where a command expresses intent to change state, a query expresses intent to read it.
Queries are the input to query handlers. A query carries the parameters a read
needs (an identifier, a filter, a page number); the handler answers it from a
projection and returns the result. Queries flow through domain.dispatch(query),
mirroring how commands flow through domain.process(command).
Facts
Queries target a projection.
A query is associated with a projection (a read model) via part_of, not with an
aggregate. This places it firmly on the read side: queries describe reads against
the denormalized views that projectors maintain.
Queries are immutable.
Once constructed, a query's fields cannot be changed -- assigning to a field raises
IncorrectUsageError. To issue a different read, build a new query.
Queries are validated at construction.
Field constraints (required, min_value, max_value, choices, and so on) are
enforced when the query is created, before it reaches a handler. An invalid query
raises ValidationError immediately.
Queries carry only basic data.
A query holds simple field types (and value objects) -- the parameters of a read.
It cannot contain associations (HasOne, HasMany, Reference); those belong to
aggregates.
Queries are named for what they return.
Commands are imperative (PlaceOrder); queries are named for their result
(GetOrderById, SearchOrders, ListActiveCustomers).
Queries are lightweight.
Unlike commands and events, queries carry no metadata, stream, or event-store concerns. They are plain read-intent DTOs, dispatched synchronously and answered immediately.
Best Practices
Keep queries to read parameters.
A query is a parameter object, not a place for behavior. It should not reach into repositories or compute results -- that is the query handler's job.
Model one query per read shape.
Prefer distinct queries (GetOrderById, SearchOrders) over a single
parameter-heavy query with many optional fields. Named reads are clearer and
validate more precisely.
Next steps
For practical details on defining queries and answering them, see the guide:
- Query Handlers -- Defining
queries, the
@readdecorator, and dispatching withdomain.dispatch().
For related concepts:
- Query Handlers -- The read-side handlers that answer queries.
- Projections -- The read models queries target.
- Commands -- The write-side counterpart (intent to change state).