Jump to Category
| Core CQRS Principles | The Command Side |
| The Query Side & Data Consistency | ⚡ CQRS with Event Sourcing |
| ️ Architecture & Implementation |
Core CQRS Principles
1. What is the fundamental principle of CQRS?
CQRS (Command Query Responsibility Segregation) is an architectural pattern that separates the models used for updating data (Commands) from the models used for reading data (Queries). The core idea is that you can use a different model to update information than the model you use to read it. This is a departure from traditional CRUD systems where the same object model is used for both reading and writing.
Read Martin Fowler’s defining article on CQRS.2. What problem does CQRS solve? When should you consider using it?
CQRS solves the problem of a single, one-size-fits-all data model becoming overly complex. In many applications, the requirements for reading data (e.g., performance, complex views) are very different from the requirements for writing data (e.g., transactional consistency, validation).
You should consider using CQRS in:
- Complex Domains: Where the business logic for writes is significantly different from how data is presented.
- High-Performance Applications: It allows you to create highly optimized, denormalized read models for fast queries, without compromising the integrity of your transactional write model.
- Collaborative Domains: Where multiple actors operate on the same data, and contention is high.
It is overkill for simple CRUD applications.
3. Is it necessary for the read and write models in a CQRS system to use different databases?
No, it is not strictly necessary. You can implement CQRS with a single database. In this model, you would still have separate object models for commands and queries in your application code, but they would both operate on the same underlying database (e.g., writing to normalized tables and reading from denormalized views).
However, the pattern becomes much more powerful when you use physically separate databases, allowing you to choose the best technology for each job (e.g., a relational database for writes and a document database or search index for reads).
4. How does CQRS relate to Domain-Driven Design (DDD)?
CQRS and DDD are highly complementary.
- The **Command Side** of a CQRS system maps well to the DDD concept of an **Aggregate**. The command handler loads an Aggregate, executes a business method on it (enforcing invariants), and the repository persists the result. This creates a rich, behavior-driven write model.
- The **Query Side** provides the data needed for views and UIs, but it does not contain complex domain logic. This aligns with the DDD principle of keeping the core domain model focused on state changes and business rules.
5. What is the role of an event bus or message broker in a CQRS architecture?
When the read and write models are physically separate, an event bus is the mechanism used to keep them synchronized. After the command side successfully processes a command and updates its state, it publishes an **event** describing the change (e.g., `OrderPlaced`).
Event handlers (or projectors) subscribe to these events and are responsible for updating the denormalized read models. This asynchronous communication decouples the read and write sides and is the primary source of eventual consistency in a CQRS system.
The Command Side
6. What is the ideal structure of a Command object?
A Command should be a simple, immutable Data Transfer Object (DTO). It should be named with an imperative verb (e.g., `CreateUser`, `UpdateAddress`) and contain all the data necessary to execute that action. It should not contain any business logic itself; its only purpose is to carry intent and data from the client to the command handler.
7. Where should command validation be performed?
Validation should occur in layers:
- Syntactic Validation: The receiving endpoint (e.g., API controller) should perform initial, synchronous validation on the command object itself. This includes checking for required fields, correct data types, and value formats. If this fails, an immediate error is returned to the client.
- Business Rule Validation: Deeper validation that may require checking the current state of the system should be performed within the command handler or, ideally, within the domain model (the Aggregate) itself. This ensures that business invariants are always protected.
8. Should a command handler return data?
In a strict CQRS implementation, commands should not return data. They are task-based and their return type should be `void` or, at most, an acknowledgement of success or failure (e.g., an `ack`/`nack` or an exception). Returning data from a command handler blurs the line between commands and queries. If the client needs to see the result of the state change, it should perform a separate query to the read model, which will be eventually consistent.
9. How do you ensure command handlers are idempotent?
Idempotency is crucial if the command bus or client might retry sending a command. A common pattern is to include a unique **command ID** in every command object. The command handler (or a decorator/middleware) can then keep track of processed command IDs in a persistent store. Before executing the business logic, it checks if the command ID has already been processed. If so, it can safely skip the execution. This is especially important in systems that are not using Event Sourcing, where re-executing a command could lead to incorrect state changes.
Read about Idempotence in messaging systems.The Query Side & Data Consistency
10. What is a “Projection”?
A **Projection** is the process of taking events from the write side and using them to build or update a denormalized read model. The read model itself is also often called a projection.
For example, an event handler could listen for `OrderPlaced` and `OrderShipped` events. Its projection logic would update a “read” table called `order_summaries` with the relevant data, which is specifically shaped to be queried efficiently by a user interface.
11. What are some strategies for handling eventual consistency in the UI?
After a user executes a command, the read model will not be updated instantly. The UI must handle this “consistency gap.”
Strategies include:
- Client-Side Prediction: The UI can optimistically update itself based on the command that was sent, assuming it will succeed. It can then reconcile its state once the updated read model is available.
- Polling: The client can poll the read model API until the expected change appears.
- Server Push: The server can push a notification (e.g., via WebSockets) to the client once the projection has been updated, signaling the UI to refetch the data.
- Returning an Identifier: The command can return an identifier, and the UI can show a “processing” state while it waits for the query result to become available.
12. How do you handle rebuilding a read model projection from scratch?
There are times when you need to change a read model’s schema or fix a bug in its projection logic. To do this, you need to rebuild it. The ability to rebuild projections is a key advantage of systems that use Event Sourcing.
The process is:
- Create a new table/index for the new version of the read model.
- Run a process that reads the entire event stream from the beginning of time and applies the new projection logic, populating the new table.
- Once the new projection is caught up, you can atomically switch the application to start querying from it.
- The old projection can then be deleted.
13. How would you design a query model for a screen that needs data from multiple bounded contexts?
This is a classic use case for a dedicated read model. You would create a new service whose sole responsibility is to provide the data for this specific screen. This service would have its own data store (its read model). It would subscribe to events from all the relevant bounded contexts (e.g., `UserRegistered` from the identity context, `OrderPlaced` from the sales context) and build a composite, denormalized view of the data. This keeps the query simple and performant, and the source services remain completely decoupled.
14. What are some good technology choices for a read store?
The choice depends entirely on the query patterns you need to support.
- For simple key-value lookups: **Redis** or **DynamoDB**.
- For flexible queries on structured documents: **MongoDB** or **PostgreSQL** (using its JSONB capabilities).
- For full-text search and complex faceting: **Elasticsearch** or **OpenSearch**.
- For simple tabular data: A relational database like **PostgreSQL** or **MySQL**.
CQRS with Event Sourcing
15. What is Event Sourcing, and how does it relate to CQRS?
Event Sourcing** is a pattern where you persist the state of an entity as a sequence of immutable events, rather than just its current state. The current state is derived by replaying these events.
While CQRS and Event Sourcing are separate patterns, they are a natural fit:
- The **event stream** becomes the perfect write model for the command side. Commands are validated against the current state (derived from the stream), and if successful, they produce new events that are appended to the stream.
- The same event stream also provides the ideal mechanism for updating the query side. Projections can subscribe to the event stream to build and maintain the read models.
16. What are snapshots in Event Sourcing?
For long-lived aggregates with thousands of events, replaying the entire event stream every time you need to load the aggregate can become slow. A **snapshot** is a pre-computed summary of an aggregate’s state at a specific point in time (or event version).
To load the aggregate, you first load the most recent snapshot and then only replay the events that have occurred *since* that snapshot was taken. This significantly speeds up the process of reconstituting the aggregate’s state.
17. How do you handle schema evolution for events in an event-sourced system?
Since events are immutable, evolving them is a major challenge. Common strategies include:
- Upcasting: When an old version of an event is read from the store, it is transformed (“upcasted”) on the fly into the new version before being applied to the domain model. This is the most common approach.
- Lazy Transformation: Keep multiple versions of the event handlers/projectors in the code.
- Copy-and-Transform Migration: In rare cases for major changes, you can read the entire event stream, transform the events to a new format, and write them to a completely new stream.
Architecture & Implementation Challenges
18. What is the Transactional Outbox pattern and why is it essential in a CQRS system?
The Outbox Pattern ensures that events are reliably published *after* the state of the write model has been successfully committed to its database. It solves the “dual write” problem where you might successfully save to your database but fail to publish the corresponding event to the message broker.
It works by saving the business state and the event(s) to be published together in the same atomic database transaction. The event is saved to an “outbox” table. A separate process then reliably reads from this outbox table and publishes the events to the broker. This guarantees that an event is only published if the corresponding state change was successful.
Learn about the Transactional Outbox pattern.19. How do you handle out-of-order event processing?
This is a difficult problem in distributed systems. Strategies include:
- Idempotency with Versioning: Include a version number or timestamp in the event. The event handler can then ignore events that are older than the state it has already processed.
- Buffering and Reordering: A consumer can buffer events for a short period and try to reorder them based on a sequence number before processing. This adds complexity and latency.
- Design for Commutativity: If possible, design the business logic so that the order of events does not matter. This is the most robust solution but is not always possible.
20. What is the Saga pattern and how does it relate to CQRS?
A **Saga** is a long-running process that coordinates transactions across multiple bounded contexts or services. It’s a failure management pattern. When a command in a CQRS system triggers a process that spans multiple services, a Saga can be used to manage that process.
For example, an `OrderPlaced` event might start an `OrderProcessingSaga`. The Saga would then send a `ReserveInventory` command to the inventory service and a `ProcessPayment` command to the payment service. The Saga listens for reply events (`InventoryReserved`, `PaymentProcessed`) and manages compensating actions (`ReleaseInventory`) if any step fails.
Explore the Saga pattern on microservices.io.21. How do you test a CQRS system?
You test the two sides differently:
- Command Side: This can be unit tested easily. You can write a test that sends a specific command to a handler and then asserts that the correct events were produced as a result. This is a “Given-When-Then” style of testing.
- Query Side: A projector/event handler can be tested by feeding it a known sequence of historical events and then asserting that the state of the generated read model is correct.
End-to-end tests would involve sending a command and then polling the query side until the expected state appears, accounting for eventual consistency.


