Queries

A query is a read: it returns data and never mutates anything. Where a command names the aggregate it writes, a query names the entity it returns and the method it reads by — either a single record (get) or a paginated page (list). Because reads and writes are separate constructs, a query is free to be shaped purely for how it will be consumed.

The simplest query loads one record by id:

sub-domains/catalog/queries/product.vishwakarma
query GetProduct {
  returns    Product
  method     get
  visibility public
  doc        "Load a product by id."
  systems    [CatalogSystem]

  where "this.id == id"

  id uuid required
}

where — binding inputs to the read

The where clause is the query's filter, written over this (the candidate row) and the query's own inputs. In GetProduct, where "this.id == id" matches the row whose id equals the id input — a lookup by primary key. The input fields at the bottom (id uuid required) are exactly the same input mechanism a command uses; here they parameterize the filter instead of a write.

A where can bind any field. Chowk's ListOrdersByCustomer scopes the result to one customer:

sub-domains/ordering/queries/order.vishwakarma
query ListOrdersByCustomer {
  returns    Order
  method     list
  visibility public
  doc        "List a customer's orders, most recent first."
  systems    [OrderingSystem]

  where "this.customer_id == customer_id"

  pagination        offset
  default_page_size 20
  default_sort      [-created_at]

  customer_id uuid required
}

get versus list

The method decides the shape of the response:

  • get returns a single record (or a not-found). Use it for lookups by a unique key — GetProduct, GetOrder.
  • list returns a page of records plus a total count, so a client can render a table and paginate through it. Use it for collections — ListProducts, ListOrdersByCustomer.

A list query carries three extra knobs that a get does not:

ClauseWhat it controls
pagination offsethow pages are walked — offset-based paging
default_page_size 20the page size when the client doesn't ask for one
default_sort [-created_at]the default ordering — here, newest first (the leading - means descending)

ListProducts shows the same three on the catalog side, with no where at all — an unfiltered, newest-first listing of the whole catalog:

sub-domains/catalog/queries/product.vishwakarma
query ListProducts {
  returns    Product
  method     list
  visibility public
  doc        "List catalog products, most recent first."

  pagination        offset
  default_page_size 20
  default_sort      [-created_at]
}

Reads honor the same boundaries as writes

A query is not a way around your access rules. It runs under the same resolved caller as a command, so tenancy isolation and authorization apply to reads exactly as they do to writes: a caller only ever sees rows their identity is allowed to see, and a query can carry a policy [...] belt to gate the read itself (see Policies). Modeling a read as a first-class query — rather than exposing the table — is what lets those boundaries hold on the read side too.