Skip to content

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 @read decorator, and dispatching with domain.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).