From design-patterns
Use when read and write performance requirements diverge significantly, complex reporting is painful through the domain model, or you need to scale reads and writes independently. Apply before splitting any command/query handler or building read-optimised projections. Covers Command Query Responsibility Segregation — including when it is overkill — with guidance on applying only as much separation as the problem demands.
How this skill is triggered — by the user, by Claude, or both
Slash command
/design-patterns:cqrsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
*Sources: Greg Young (2010); Martin Fowler — bliki/CQRS; Vaughn Vernon — Implementing DDD (2013)*
Sources: Greg Young (2010); Martin Fowler — bliki/CQRS; Vaughn Vernon — Implementing DDD (2013)
Before applying CQRS, confirm the pain exists. Fowler's warning: "CQRS is a significant mental leap for all concerned, so shouldn't be tackled unless the benefit is worth that leap."
Commands change state and return nothing (or minimal acknowledgement). Queries read state and change nothing — calling a query twice returns the same result given no intervening commands.
These two concerns have fundamentally different requirements. Optimise them separately.
Minimal Moderate Full
────────────────────────────────────────────────────────────
Single model Separate handlers Separate models
Same DB Same DB Separate DBs
No projections Direct DB reads Event-driven projections
in query handlers Read stores
Most systems need at most the moderate level. Full CQRS with separate read/write stores and event-sourced projections is a significant commitment — justified only in specific circumstances.
A command is an intent to change state. It may be rejected. It returns nothing meaningful on success (or just an ID if a resource was created).
# Command — named in imperative form
@dataclass
class SubmitOrderCommand:
order_id: OrderId
submitted_by: UserId
class SubmitOrderHandler:
def handle(self, cmd: SubmitOrderCommand) -> None:
# Validate first — reject invalid commands before touching the domain
order = self._orders.find_by_id(cmd.order_id)
if order is None:
raise OrderNotFound(cmd.order_id)
order.submit() # domain logic enforced in the aggregate
self._orders.save(order)
self._events.publish(order.pop_events())
Rules:
A query reads and returns data. It has no side effects. It does not need to go through the domain model.
# Query — named after what it returns
@dataclass
class GetOrderSummaryQuery:
order_id: OrderId
class GetOrderSummaryHandler:
def handle(self, query: GetOrderSummaryQuery) -> OrderSummaryDto:
# Can read directly from DB — no domain model required
row = self._db.query_one("""
SELECT id, status, customer_name, total_cents, item_count, submitted_at
FROM order_summaries
WHERE id = %s
""", (str(query.order_id),))
if row is None:
raise OrderNotFound(query.order_id)
return OrderSummaryDto(**row)
Key insight: queries bypass the domain model entirely. They read from the database (or a read-optimised projection) directly. This is where significant performance gains come from — no object graph loading, no mapping overhead.
In full CQRS, the read side maintains denormalised views built by consuming domain events. These are optimised for specific query shapes.
class OrderSummaryProjection:
"""Keeps the read model up to date by consuming domain events."""
def on_order_submitted(self, event: OrderSubmitted) -> None:
self._read_db.upsert('order_summaries', {
'id': str(event.order_id),
'status': 'submitted',
'customer_id': str(event.customer_id),
'total_cents': event.total.in_cents(),
'item_count': event.item_count,
'submitted_at': event.submitted_at.isoformat()
})
def on_order_shipped(self, event: OrderShipped) -> None:
self._read_db.update('order_summaries',
where={'id': str(event.order_id)},
values={'status': 'shipped', 'shipped_at': event.shipped_at.isoformat()}
)
Trade-off: read models are eventually consistent with the write side. The lag is typically milliseconds to seconds but must be explicitly acceptable to the business for every use case.
npx claudepluginhub entelligentsia/skillforge --plugin design-patternsImplements CQRS (Command Query Responsibility Segregation) for scalable architectures. Use when separating read/write models, optimizing query performance, or building event-sourced systems.
Separate command (write) and query (read) models for complex domains. Use when read/write patterns diverge significantly or when audit/consistency requirements demand immutability.
Implements CQRS (Command Query Responsibility Segregation) for scalable architectures. Use when separating read/write models, optimizing query performance, or building event-sourced systems.