Microservice Course
CQRS (Command Query Responsibility Segregation)
CQRS is a software design pattern that stands for Command Query Responsibility Segregation.
The core idea: separate the part of the system that changes state (Commands) from the part that reads state (Queries). These two concerns have fundamentally different characteristics — write paths need strong consistency, audit trails, and business rule validation; read paths need to be fast, flexible, and shaped exactly for what the UI or API consumer needs. Trying to serve both from the same model is a common source of complexity and performance problems.

Commands alter the state of an object or entity. They represent intent: CreateOrder, UpdateCustomerAddress, CancelShipment. A command either succeeds and changes state, or fails with a validation error — it does not return data (other than acknowledgement or an ID).
Queries return the state of an object or entity. They are read-only and have no side effects — running a query never changes the system's state (this is the Command-Query Separation principle that Bertrand Meyer defined; CQRS applies it at the architectural level).
Why Split Them?
In a conventional architecture, reads and writes share the same domain model and the same database tables. This creates tension:
- The write model is normalized for consistency and integrity (foreign keys, constraints, transactions). The read model needs denormalized, pre-joined data for performance.
- Scaling reads and writes independently is difficult when they share the same tables and connection pool.
- Read operations often need to aggregate data across many entities; modelling that efficiently in a write-optimized schema requires expensive joins or N+1 queries.
With CQRS:
- The write side maintains a normalized, transactionally consistent model (e.g., a relational database). All business rules and invariants are enforced here.
- The read side maintains one or more read-optimized projections — denormalized views tailored to specific queries. These are typically stored in a different database or schema: Elasticsearch for full-text search, Redis for low-latency key-value lookups, a materialized view in PostgreSQL, or a document store.
- The read side is updated asynchronously when the write side emits events (which is where CQRS pairs naturally with Event Sourcing and a message bus like Kafka).
Implementation Sketch
// Command side — changes state public class CreateOrderCommand { private final String customerId; private final List<OrderItem> items; // constructor, getters... } @Service public class OrderCommandService { private final OrderRepository orderRepository; private final ApplicationEventPublisher eventPublisher; public String createOrder(CreateOrderCommand command) { Order order = new Order(command.getCustomerId(), command.getItems()); orderRepository.save(order); eventPublisher.publishEvent(new OrderCreatedEvent(order.getId(), order)); return order.getId(); } } // Query side — reads a pre-projected view @Service public class OrderQueryService { private final OrderSummaryRepository summaryRepository; // read-optimized store public OrderSummaryDTO getOrderSummary(String orderId) { return summaryRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException(orderId)); } }
Benefits
- Read and write models can be optimized independently — choose the right database technology for each concern.
- Read side can scale horizontally without affecting the write side (and vice versa).
- Queries are simpler and faster because the read model is pre-projected for the exact query shape needed.
- Clear separation of concerns makes the codebase easier to reason about.
- Natural fit with Domain-Driven Design — the command side maps to domain aggregates and business rules; the query side maps to user interface needs.
When to Use (and When Not to)
CQRS adds real complexity — two models to maintain, eventual consistency between them, more infrastructure to operate. It pays off when:
- Read and write workloads have very different scale characteristics (e.g., millions of reads per second, thousands of writes).
- Complex domain logic on the write side would pollute and complicate simple read queries if they shared a model.
- You need multiple read-optimized projections of the same data for different consumers (mobile, web, analytics).
For simple CRUD applications, CQRS is almost certainly over-engineering. Don't apply it by default — apply it where the complexity is justified.
Event Sourcing
Event Sourcing defines an approach where all changes to an object or entity are stored as a sequence of immutable events in an event store, rather than storing only the current state.
Instead of a customer table with a row representing the current state of a customer, you have an customer_events stream: CustomerRegistered, AddressUpdated, EmailVerified, AccountSuspended. The current state is derived at any time by replaying the events from the beginning (or from a snapshot checkpoint).
How It Works
// Events are immutable facts — things that happened public class OrderCreatedEvent { private final String orderId; private final String customerId; private final List<OrderItem> items; private final Instant occurredAt; // constructor, getters — no setters, events are immutable } public class OrderShippedEvent { private final String orderId; private final String trackingNumber; private final Instant occurredAt; } // The aggregate reconstructs its state by applying events public class Order { private String id; private OrderStatus status; private List<OrderItem> items; public static Order reconstitute(List<DomainEvent> events) { Order order = new Order(); events.forEach(order::apply); return order; } private void apply(DomainEvent event) { if (event instanceof OrderCreatedEvent e) { this.id = e.getOrderId(); this.items = e.getItems(); this.status = OrderStatus.CREATED; } else if (event instanceof OrderShippedEvent e) { this.status = OrderStatus.SHIPPED; } // ... } }
Benefits
- Complete audit trail: Every state change is recorded as an immutable event. You can always answer "what happened to this entity and when?" — something impossible with current-state-only storage.
- Temporal queries: You can reconstruct the state of an entity at any point in time by replaying events up to that timestamp.
- Event replay: If you add a new read projection or fix a bug in a projection, you can replay historical events to rebuild the view from scratch.
- Natural integration with messaging: Events are already first-class — publishing them to Kafka or RabbitMQ for other services to consume is straightforward.
- Debugging: The event stream is a complete record of what happened, making post-mortem analysis much more powerful.
Challenges
- Querying is harder: You can't query the event store with a
SELECT * WHERE status = 'SHIPPED'— you need to build and maintain read-side projections (which is why Event Sourcing almost always pairs with CQRS). - Schema evolution: Changing the structure of historical events is non-trivial. Events are immutable once written, so you need versioning strategies (upcasting events when reading old formats).
- Performance: Replaying thousands of events to reconstruct a single aggregate is slow. Mitigated with periodic snapshots — store a checkpoint of the aggregate's state at a point in time, then replay only events since the snapshot.
- Eventual consistency: Read projections are updated asynchronously; there is a window where a query returns stale data immediately after a write.
- Operational complexity: Event stores (EventStoreDB, Axon Server, Kafka with compaction, or a purpose-built append-only table) require additional infrastructure and operational knowledge.
When to Use
Event Sourcing is a powerful but heavyweight pattern. Use it when the domain genuinely needs the audit trail, temporal queries, or event replay capability — financial transactions, order management, medical records, compliance-sensitive domains. Don't apply it as a default architectural choice; in most simple domains, current-state persistence is simpler and entirely sufficient.
CQRS + Event Sourcing Together
These two patterns are frequently combined but are independent — you can use CQRS without Event Sourcing (separate read/write models backed by different current-state tables) and Event Sourcing without CQRS (though querying becomes very painful without separate read projections). When used together, the write side stores events, events are published to a message bus, and projectors on the read side consume those events to build denormalized query views. Frameworks like Axon Framework provide opinionated support for this combination in the Java/Spring ecosystem.