CQRS Documents by Greg Young — An English Reader's Guide
This post is an English-language reader's guide to Greg Young's 56-page white paper “CQRS Documents” (available from cqrsinfo.com). It summarises each of the six chapters in the author's own words and adds practical commentary. It is not a reproduction of the original text — if you want the full argument, please read the original directly.
56
Original pages
6
Chapters
summary
Format
2010
Original era
ℹ️ About the source
Original author: Greg Young. Source: http://cqrsinfo.com. The paper was distributed freely by the author for educational use. This post summarises and comments on the paper; it does not reproduce it.
Chapter 1 — A Stereotypical Architecture
original p.2-7Young opens by sketching what he calls the “stereotypical” architecture that most teams reach for by default: a relational (or similar) data store at the bottom, domain objects above it, an application-services layer wrapping the domain, and a remote facade exposing that layer to clients. Clients interact by requesting a DTO, displaying it, editing it, and posting it back.
His analysis is even-handed. The architecture is genuinely simple, it is generic enough to apply to almost any project, and there is a rich ecosystem of tooling (ORMs, auto-mappers) that amortises most of the plumbing cost. For around 80% of projects, it really is “good enough.”
The decisive objection is not about performance or elegance but about modelling capacity. Because the remote facade is data-oriented, the only verbs the domain ever sees are the four CRUD verbs. Young argues that this makes it effectively impossible to practise Domain Driven Design on top of this architecture: you end up with something even worse than an Anemic Domain Model — “a glorified Excel spreadsheet,” in his words. The chapter closes by noting that the scalability of such a system is bottlenecked on the data store, but points out that most systems don't actually need to scale, so scalability alone isn't the main reason to move away.
Key takeaway
The default DTO up/down architecture is fine for most low-value CRUD work. It is the wrong starting point when the business value of the system comes from rich behaviour in the domain.
Chapter 2 — Task Based User Interface
original p.9-16Chapter 2 argues that the stereotypical architecture silently erases the user's intent. Because the client posts back the post-edit state of an object, the server only ever sees the result and has to guess the verb behind it. Young's remedy is to rethink the UI itself: design screens that guide users through specific tasks, and have the client emit explicit Commands for those tasks (e.g. “Complete a Sale”, “Approve a Purchase Order”) instead of round-tripping DTOs.
He leans on Microsoft's older research on “Inductive UI” to justify the shift, noting the familiar findings that users rarely build a correct mental model of deductive software and often re-learn the same procedure every time. Task-oriented screens fix this by collapsing each user intent into a focused mini-workflow, which also happens to give developers an obvious place to construct Command objects.
A Command, in his definition, is a simple serialisable object that carries only the data needed to perform one operation. It is always written in the imperative(“DeactivateInventoryItem”, not “InventoryItemDeactivated”), and it is something the domain is allowed to reject. This linguistic discipline — which Young returns to repeatedly — is what prevents Commands from collapsing back into CRUD verbs like “ChangeAddress” and is how the ubiquitous language of the domain actually grows verbs at all.
Chapter 3 — CQRS: Command and Query Separation
original p.17-24Here Young finally introduces CQRS by name. He traces it back to Bertrand Meyer's Command-Query Separation principle (methods either do something or return something, never both), and notes that for a long time CQRS was treated as just CQS applied at a higher level until it was eventually recognised as a distinct pattern. The mechanical move is small: split a service like CustomerService into a write-side service (CustomerWriteService) and a read-side service (CustomerReadService).
The interesting consequences come from looking at each side independently. Writes want consistency, normalised data and don't need to scale very much. Reads can almost always be eventually consistent, benefit from denormalised data, and usually do need to scale. Trying to serve both from one model is what creates many of the real-world smells: repositories cluttered with read methods, getters opened up just to feed DTOs, prefetch paths bent around queries, and aggregate boundaries distorted by read-side concerns.
Young's answer is a Thin Read Layer that bypasses the domain entirely and projects DTOs straight from the database — handwritten SQL, stored procedures, a small convention-based mapper, whatever fits. With reads pulled out of the domain, the write side is free to focus purely on behaviour, and the two sides can even live on two different data stores kept in sync via events. That last point is the bridge into the next chapter.
A single model cannot be simultaneously optimal for searching, reporting and transaction processing.
Chapter 4 — Events as a Storage Mechanism
original p.25-40Chapter 4 is the longest and, arguably, the philosophical heart of the paper. Young makes the case that storing current state is a relatively recent convention and not the only option. Instead, the system can store the sequence of Domain Events— always named in the past tense (“CustomerRelocated”, “CargoShipped”) — and derive current state by replaying them.
He spends real effort distinguishing this concept from Martin Fowler's and Streamlined Object Modelling's uses of the term “Domain Event,” and uses that discussion to sharpen the Command/Event split: Commands are imperative requests the domain can reject; Events are past-tense records of things that have already happened and cannot be undone. Mixing the two creates dual definitions, forces mutability into events, and muddies the ubiquitous language.
The chapter then walks through the practical properties of an event-sourced store: there is no delete, only reversal transactions; the store is append-only, which is easy to partition and scale; loading and saving aggregates become conceptually simpler because the aggregate itself knows what happened to it; and rolling snapshots can be introduced as a heuristic to keep replay cost bounded without changing the conceptual model. A further section on “impedance mismatch” points out that unlike the object-relational mismatch, events and the domain model share the same paradigm, so the cost of bridging them is much lower.
The chapter closes with a business-facing argument: storing events preserves the how, not just the what, which lets the business ask questions about past behaviour that nobody thought to track at the time. That capability is only worth paying for where the business draws real competitive advantage from the domain, and Young is careful to say so.
Chapter 5 — Building an Event Storage
original p.41-49Chapter 5 is the pragmatic chapter. Young shows that you can bootstrap an event store on top of an RDBMS with just two tables: one for events (aggregate id, serialized data, version) and one for aggregates (aggregate id, type, current version). The store only ever needs two operations — load all events for an aggregate ordered by version, and append a set of events under an optimistic concurrency check — which makes the surface area refreshingly small.
He then layers on rolling snapshots as a separate table keyed by aggregate id and version, with a background snapshotter process that periodically picks aggregates whose event count has grown beyond a threshold and writes a snapshot asynchronously. Crucially, he argues against putting snapshots inline in the event log itself: inline snapshots force them to be at the latest version and create concurrency-failure loops for busy aggregates. A separate snapshot table lets a snapshot remain valid at whatever version it was taken.
The chapter ends with a discussion of using the event store as a queue. Publishing events to a message broker in a two-phase commit is expensive and painful at low latencies. Instead, add a monotonically increasing sequence number to the events table and let a chaser process tail it, publishing events asynchronously and tracking its own progress. The initial write stays a single disk operation, and publication is decoupled from the write path.
Chapter 6 — CQRS and Event Sourcing Together
original p.50-56The final chapter argues that CQRS and Event Sourcing are genuinely symbiotic. CQRS removes the need to query the event-sourced write side (the only write-side query is get-by-id), and event sourcing gives CQRS a natural write-side persistence model with no second relational schema to maintain.
A cost comparison makes the case that the combined architecture is roughly the same amount of work as the stereotypical one — the work is different, not greater. The object-relational impedance mismatch is gone on the write side, replaced by a much simpler mismatch between events and a relational read model, which is easy to bridge with event handlers. Integration is no longer an afterthought: because every behaviour in the system produces events, the system is effectively always integrated.
The most interesting part of this chapter is its discussion of team shape. Because the architecture decomposes cleanly into three loosely coupled concerns (client, domain, read model), Young suggests it opens up parallelisation and specialisation options that vertical slicing cannot: senior domain-focused engineers can spend most of their time with domain experts, while the read model — with its concrete, easily specified contracts — is a natural candidate for outsourcing. He stresses that teams are not obliged to work this way, but that the architecture makes the option available.
Takeaways for Practitioners
- CRUD erases intent. The first step toward CQRS is not technical — it is recognising that a DTO up/down architecture gives your domain nowhere to grow verbs.
- Task Based UI is the on-ramp to Commands. If screens guide users through specific tasks, constructing Command objects becomes natural rather than forced.
- Write Commands in the imperative, Events in the past tense. This single discipline keeps the ubiquitous language coherent.
- A Thin Read Layer is often the cheapest win. Even without event sourcing, projecting DTOs directly from the database removes most of the read-side smells in a typical DDD project.
- Event sourcing pays off where the business cares about the “how.”Apply it selectively to bounded contexts that carry competitive advantage; don't retrofit it system-wide on principle.
- An event store can start small. Two tables, two operations, optimistic concurrency, and you have a working foundation. Snapshots and publication can be layered on later.
💡 Start small
Young himself ends the paper by noting that not every team needs the full split. Begin by separating read and write methods in a single service, add a thin read layer where read queries are painful, and only reach for event sourcing in bounded contexts where the business genuinely benefits from an immutable history.
Original author: Greg Young · Source: http://cqrsinfo.com · Reader's guide: Treeru (2026-04-14)
Comments
(4)Log in to leave a comment.
A useful condensed map of Greg Young's paper. The chapter summaries make it easy to decide which sections to re-read in the original.
The commentary on why Anemic Domain Models emerge from CRUD-centric architectures is spot on. Good pointer for new team members.
I like that this is framed as a reader's guide rather than a reproduction. Gives me enough context to open the original with confidence.
Related Posts
© 2026 TreeRU. All rights reserved.
All content is copyrighted by TreeRU. Unauthorized reproduction without attribution is prohibited.