9 views
CQRS is not a badge — it is an operational bill. Dual models mean dual migrations, dual failure modes, and eventual consistency bugs that support tickets will call “random.” Seniors introduce separate read models only when measured query pain exceeds that tax, with explicit lag SLOs and a rollback story.
⚡ TL;DR: Split when write-optimized schema cannot meet p95 read latency or fan-out joins without melting writers. Keep a single model until then. Measure lag, dual-deploy cost, and consumer breakage. Pair with Read-Your-Writes on Global Tables and DynamoDB Streams Outbox.
CQRS Boundary Criteria: production guidance
python
❌ Splitting because a conference talk said “commands and queries must diverge” — that is fashion, not criteria.
# cqrs/decision.py
from dataclasses import dataclass
@dataclass
class CqrsSignals:
write_p95_ms: float
read_p95_ms: float
read_qps: float
join_depth: int
projection_lag_slo_ms: float
team_can_own_two_schemas: bool
def should_split(s: CqrsSignals) -> tuple[bool, str]:
if not s.team_can_own_two_schemas:
return False, "no_owner_for_projection"
if s.read_p95_ms < 80 and s.join_depth <= 2:
return False, "single_model_still_fast"
if s.write_p95_ms > 100 and s.read_qps > 500 and s.join_depth >= 3:
return True, "write_path_contends_with_heavy_reads"
if s.projection_lag_slo_ms < 200 and s.read_qps > 2000:
return True, "hot_read_path_needs_denorm"
return False, "tax_not_justified"
Boundary patterns that earn their keep
| Pattern | When it pays | Hidden cost |
|---|---|---|
| Same DB, denormalized read tables | Heavy joins on OLTP | Trigger/CDC dual-write bugs |
| Event-sourced writes + projection | Audit + many read shapes | Replay tooling, lag SLOs |
| Separate store (OpenSearch / Dynamo) | Search / fan-out reads | Schema drift, reindex jobs |
| Per-tenant read replicas | Isolation | Failover and lag UX |
Projection contract with lag SLO
typescript
UI paths that just created an entity must tolerate lag — sticky sessions to the write model or wait-for-projection tokens beat lying to the user.
// cqrs/projection.ts
export async function projectOrderPlaced(evt: OrderPlaced) {
await readDb.upsert({
pk: `order#${evt.orderId}`,
customerId: evt.customerId,
total: evt.total,
status: "placed",
version: evt.version, // ✅ monotonic — reject older
projectedAt: Date.now(),
});
}
export async function assertLag(orderId: string, writeAt: number, sloMs = 500) {
const row = await readDb.get(`order#${orderId}`);
if (!row || row.projectedAt - writeAt > sloMs) {
metrics.hit("ProjectionLagBreach");
// ✅ Fall back to write-side get for read-your-writes sessions
return "fallback_write_model";
}
return "ok";
}
Operational gate: dual deploy tax
Before merging the split, answer:- Who owns projection failures on-call?
- Can you rebuild the read model from events in < 4 hours?
- Do contract tests fail the PR when event fields drop?
- Is there a feature flag to serve reads from the write model?
Closing checklist
✅ Dos – ✅ Score latency, join depth, and ownership before splitting – ✅ Publish projection lag SLOs and breach metrics – ✅ Version projections; reject stale events – ✅ Provide read-your-writes fallback for create flows – ✅ Prove rebuild time from the event log ❌ Don’ts – ❌ Don’t CQRS every CRUD microservice by default – ❌ Don’t ignore dual-schema migration cost – ❌ Don’t expose lag as “eventual” without UX handling – ❌ Don’t let projections silently drop fields – ❌ Don’t split when an index or materialized view would doRelated reading
- Read-Your-Writes on Global Tables: Survive Cross-Region Replication Lag
- DynamoDB Streams Outbox: Domain Events Without Dual-Write Failures
- EventBridge Schema Evolution: Additive Changes Without Breaking Consumers
- Saga Compensations Under Partial Failure
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: Feature Stores vs Inline Compute: Real-Time Decisions Without Training Skew - CheatCoders