CQRS — Command Query Responsibility Segregation
Separate write models (commands) from read models (queries) when their shape, load, or consistency needs differ.
When to use
- Read-heavy with complex projections (denormalized views)
- Read and write have very different scaling needs
- Natural fit with Event Sourcing
Tradeoffs
- Two models to maintain + eventual consistency between write and read side
- Overkill for simple CRUD
- Go
- Python
type PlaceOrderCmd struct { CustomerID string; Total float64 }
type OrderSummaryQuery struct { CustomerID string }
type Handler interface{ Handle(msg any) (any, error) }
func dispatch(h map[string]Handler, msg any) (any, error) {
key := fmt.Sprintf("%T", msg)
if handler, ok := h[key]; ok {
return handler.Handle(msg)
}
return nil, fmt.Errorf("no handler for %s", key)
}
from dataclasses import dataclass
from typing import Any
@dataclass
class PlaceOrderCmd:
customer_id: str
total: float
@dataclass
class OrderSummaryQuery:
customer_id: str
handlers: dict[type, Any] = {}
def dispatch(msg: Any) -> Any:
handler = handlers.get(type(msg))
if not handler:
raise ValueError(f"No handler for {type(msg)}")
return handler(msg)
Gotcha: CQRS ≠ Event Sourcing. You can have one without the other.