CQRS with Async Projection: Two Models, One Truth

May 3, 2026


In a CRUD system the same tables serve writes and reads, which means every choice is a compromise. Normalize for write integrity and your dashboard queries do twelve joins. Denormalize for the dashboard and your transactional updates fan out across rows. CQRS resolves this by giving up the idea that there is one model at all.

The write side, the command model, accepts intents like PlaceOrder or CancelShipment. It validates business rules, runs a local transaction, and persists the change. It is small, strict, and optimized for consistency. After the commit it emits an event describing what just happened. The event goes onto a bus, typically Kafka, where it becomes durable history.

The read side is a collection of projections, each subscribing to the event stream. One projector might write a denormalized order summary for the customer dashboard. Another might index orders into Elasticsearch for search. A third might aggregate into a wide column store for analytics. Each read database is shaped for its query and nothing else. When the UI needs a different view, you write a new projector. You do not migrate the write model.

The price you pay is lag. A projection is asynchronous by definition. A user who places an order and immediately reloads the page may not see it for a few hundred milliseconds, sometimes longer under load. You handle this with read-your-writes tricks at the edge, such as serving the dashboard from the write side for that user for a short window, or showing an optimistic row from local state. Most domains tolerate eventual consistency once you stop pretending they require strict consistency.

The production failure mode that costs teams a weekend is projection drift. A projector has a bug. For two weeks it has been writing total = subtotal and silently dropping tax. The read model and the write model disagree on millions of orders. There is no rollback because the write side is correct; only the projection is wrong.

The recovery is the part CQRS gets right. Because the event log is the source of truth, you fix the projector code, truncate the broken read table, and replay the topic from the offset where the bug started. The projection rebuilds itself. No data migration, no reconciliation script, no apology to finance. You may need to pause downstream consumers during the rebuild and you will need enough log retention to cover the regression window, which is why Kafka topics that feed projections are usually configured to retain weeks of history, not days.

Pick CQRS when read shapes diverge sharply from write shapes, or when one of the read paths needs scale the write store cannot give you.

Key takeaway

The write model owns invariants. The read models own query shapes. The event log is the contract between them, and it is also the recovery path when a projection goes wrong.

Originally posted on LinkedIn. View original.


All Rights Reserved.