CQRS Command / Query Skill
Scope: simple read/write model separation. Event sourcing and separate write/read stores are out of scope — don't introduce them here. A command/query handler is an Application Service in [[clean-arch-layer]]: a thin orchestrator with no business logic.
When to use
Adding a new use case, separating a read path from a write path, building a command or query handler, or designing the DTOs that cross the application boundary.
Commands vs Queries (normative)
- Command — changes state, returns only a confirmation/identifier (or nothing). Named as an imperative:
PlaceOrder, UpdateProfile.
- Query — reads state, returns data, never mutates. Named as a question/lookup:
GetOrderById, ListActiveUsers.
- Never mix: a command must not return domain data for display; a query must not cause side effects.
Command handler pattern
- Define the Command (input DTO with primitive/transport fields).
- Create the handler (= Application Service) with dependencies injected as ports ([[hexagonal-port]]).
- Orchestrate: validate input → map to domain → execute domain logic → persist via repository → return id/result.
- The handler is the transaction boundary.
- Business rules stay in the domain ([[ddd-entity]]); the handler only coordinates.
Query handler pattern
- Define the Query (filter / pagination parameters).
- Create the handler with read-optimized dependencies.
- Return read DTOs, never domain entities.
- Queries may bypass the domain model and read directly from a read source for performance — this is allowed (the one place infra-shaped reads are fine, behind a read port).
DTO design
- Input DTOs: validate at the boundary; map to domain objects inside the handler.
- Output DTOs: flatten domain complexity for the consumer.
- Never expose domain entities through DTOs (no leaking aggregates over the wire).
Decisions this skill settles
- Direct invocation by default; introduce a command bus only when you need cross-cutting middleware (logging, retries, transactions) across many handlers — not pre-emptively.
- Validation: structural validation (required, format) at the DTO; business-rule validation in the domain.
- Errors: return a domain error / Result for expected business failures (not found, invalid transition); reserve panics/exceptions for truly exceptional cases.
Anti-patterns to avoid
- Fat command doing many things — split into focused commands.
- Query with side effects.
- Business logic in the handler — push it into domain entities/services ([[ddd-entity]]).
- Returning domain entities from a command handler.
- A command bus / mediator added before there's a real cross-cutting need.
Verification
Examples
A command handler and a query handler (with DTOs and a read port), in Go and TypeScript: examples.md.
Reference
Command-bus trade-offs, validation placement, error/Result strategy, and the CQRS spectrum (and why event sourcing is out of scope here): reference.md.
1---2name: cqrs-command3description: CQRS Command / Query Skill4---5# CQRS Command / Query Skill67> **Scope:** simple read/write **model separation**. Event sourcing and separate write/read stores are **out of scope** — don't introduce them here. A command/query handler **is** an Application Service in [[clean-arch-layer]]: a thin orchestrator with no business logic.89## When to use1011Adding a new use case, separating a read path from a write path, building a command or query handler, or designing the DTOs that cross the application boundary.1213## Commands vs Queries (normative)1415- **Command** — _changes state_, returns only a confirmation/identifier (or nothing). Named as an imperative: `PlaceOrder`, `UpdateProfile`.16- **Query** — _reads state_, returns data, **never mutates**. Named as a question/lookup: `GetOrderById`, `ListActiveUsers`.17- **Never mix**: a command must not return domain data for display; a query must not cause side effects.1819## Command handler pattern20211. Define the **Command** (input DTO with primitive/transport fields).222. Create the **handler** (= Application Service) with dependencies injected as **ports** ([[hexagonal-port]]).233. Orchestrate: **validate input → map to domain → execute domain logic → persist via repository → return id/result**.244. The handler is the **transaction boundary**.255. Business rules stay in the domain ([[ddd-entity]]); the handler only coordinates.2627## Query handler pattern28291. Define the **Query** (filter / pagination parameters).302. Create the handler with **read-optimized** dependencies.313. Return **read DTOs**, never domain entities.324. Queries **may bypass the domain model** and read directly from a read source for performance — this is allowed (the one place infra-shaped reads are fine, behind a read port).3334## DTO design3536- **Input DTOs**: validate at the boundary; map to domain objects _inside_ the handler.37- **Output DTOs**: flatten domain complexity for the consumer.38- **Never expose domain entities** through DTOs (no leaking aggregates over the wire).3940## Decisions this skill settles4142- **Direct invocation by default**; introduce a command bus only when you need cross-cutting middleware (logging, retries, transactions) across many handlers — not pre-emptively.43- **Validation**: structural validation (required, format) at the DTO; business-rule validation in the domain.44- **Errors**: return a domain error / Result for expected business failures (not found, invalid transition); reserve panics/exceptions for truly exceptional cases.4546## Anti-patterns to avoid4748- Fat command doing many things — split into focused commands.49- Query with side effects.50- **Business logic in the handler** — push it into domain entities/services ([[ddd-entity]]).51- Returning domain entities from a command handler.52- A command bus / mediator added before there's a real cross-cutting need.5354## Verification5556- [ ] Commands return id/confirmation, not display data.57- [ ] Queries perform zero writes.58- [ ] Handlers are thin: no business rules, only orchestration.59- [ ] No domain entity crosses a DTO boundary.60- [ ] Each command handler owns one transaction.6162## Examples6364A command handler and a query handler (with DTOs and a read port), in Go and TypeScript: **[examples.md](examples.md)**.6566## Reference6768Command-bus trade-offs, validation placement, error/Result strategy, and the CQRS spectrum (and why event sourcing is out of scope here): **[reference.md](reference.md)**.