CQRS Pattern: Separating Reads from Writes
Most applications use the same data model for both reads and writes. You insert an order into a normalized orders table, then join across 5 tables to display the order details page. The write is simple, but the read query is complex and slow.
CQRS (Command Query Responsibility Segregation) solves this by using separate models — and often separate databases — for commands (writes) and queries (reads). Each side is optimized for its purpose.
The Core Idea
- Commands — change state.
PlaceOrder,UpdateAddress,CancelSubscription. Validated, processed, stored in a write-optimized store. - Queries — read state.
GetOrderDetails,SearchProducts,ListRecentOrders. Served from a read-optimized store (denormalized, pre-joined, cached).
The write model doesn't need to support complex queries. The read model doesn't need to enforce business rules. Each does one job well.
When CQRS Makes Sense
- Read-heavy systems — 90%+ reads. E-commerce product pages, dashboards, search results. Optimize reads without slowing writes.
- Complex read queries — joining 5+ tables or aggregating across services. A denormalized read model eliminates joins.
- Different scaling needs — reads need 100 instances, writes need 3. Scale them independently.
- Different storage needs — writes need ACID transactions (PostgreSQL), reads need full-text search (Elasticsearch) or fast lookups (Redis).
Implementation on AWS
// Command: Write to DynamoDB (normalized)
public void placeOrder(PlaceOrderCommand cmd) {
Order order = new Order(cmd.getCustomerId(), cmd.getItems());
order.validate();
dynamoDb.putItem("Orders", order.toItem());
// DynamoDB Stream automatically triggers sync Lambda
}
// Query: Read from Elasticsearch (denormalized)
public OrderView getOrderDetails(String orderId) {
return elasticsearch.get("order-views", orderId);
// Pre-joined: includes customer name, product names, status
}
Eventual Consistency: The Trade-off
With CQRS, there's a delay between a write and when the read model reflects it. When a user places an order, the order details page might take 100-500ms to show the new order.
Mitigation strategies:
- Read-your-own-writes — after a write, redirect to a page that reads from the write store, not the read store
- Optimistic UI — immediately show the expected state in the UI, then reconcile when the read model catches up
- Polling/WebSocket — notify the client when the read model is updated
CQRS Without Separate Databases
CQRS doesn't require separate databases. You can start with a single database and separate your code into command and query paths:
- Command handlers that validate and write
- Query handlers that read denormalized views (PostgreSQL materialized views, for example)
- No cross-contamination: queries never trigger writes, commands never return query data
This "CQRS lite" approach gives you the architectural benefits without the operational overhead of multiple databases.
When CQRS Is Overkill
- Simple CRUD apps — if reads and writes use the same model with simple queries, CQRS adds unnecessary complexity
- Balanced read/write ratios — if you have 50/50 reads and writes, the optimization benefit is smaller
- Small data volume — if your database handles both reads and writes comfortably, don't split prematurely
- Strong consistency required everywhere — if every read must reflect the latest write, eventual consistency is unacceptable
CQRS is a power tool. In the right context — read-heavy, complex queries, different scaling needs — it's transformative. In simple CRUD applications, it's over-engineering. Know when to use it and when a simple repository pattern is enough.
Conclusion
CQRS separates the concerns of reading and writing data, allowing each side to be optimized independently. Start with "CQRS lite" (separate code paths, same database), and evolve to separate stores when the scaling or query complexity demands it. Pair it with DynamoDB Streams or EventBridge for event-driven sync, and you get a highly scalable, maintainable architecture.
At TechTrailCamp, CQRS and event-driven patterns are covered in our Architecture and AWS tracks. You'll implement real CQRS systems with DynamoDB, Elasticsearch, and Lambda through hands-on, 1:1 mentoring.
Want to master advanced architecture patterns?
Join TechTrailCamp's 1:1 training and implement CQRS on AWS.
Start Your Learning Journey
TechTrailCamp