Unravel โบ Java concepts โบ Design patterns and architecture
CQRS and event sourcing
CQRS separates writing (commands that change state) from reading (queries over a shape built for the screen).
At a glance
A bank statement.
- 1A command (PlaceOrder) is handled by an aggregate that checks its rules and, if they hold, emits events (OrderPlaced)With event sourcing, the events are what gets saved.
- 2The aggregate current state is rebuilt by replaying its events through @EventSourcingHandler methodsSnapshots shortcut long histories.
- 3Event handlers update separate read models (tables, search indexes) tailored to queriesThe read side is eventually consistent with the write side.
- 4Axon, Spring Modulith events and plain Kafka plus a database are common building blocks
- You gain a perfect audit trail, time travel and the ability to build new read models from history
- Event handlers that update read models must be idempotent and replayable
- The event store is an append-only log per aggregate id, protected by an optimistic version check (expected sequence number) so two commands cannot both append event number 7
@Aggregatepublic class OrderAggregate { @AggregateIdentifier private String orderId; private boolean cancelled; @CommandHandler public OrderAggregate(PlaceOrder cmd) { AggregateLifecycle.apply(new OrderPlaced(cmd.orderId(), cmd.items())); } @EventSourcingHandler public void on(OrderPlaced event) { this.orderId = event.orderId(); }}- line 1 uses Aggregate, the core of CQRS and event sourcing
- line 2 uses Aggregate, the core of CQRS and event sourcing
- line 3 uses Aggregate, the core of CQRS and event sourcing
- line 7 uses CommandHandler, the core of CQRS and event sourcing
- line 8 uses Aggregate, the core of CQRS and event sourcing
- line 9 uses Aggregate, the core of CQRS and event sourcing
Read the full explanation (about 2 min)
CQRS separates writing (commands that change state) from reading (queries over a shape built for the screen). Event sourcing goes further and stores the list of events that happened instead of the current state.
A mental model
A bank statement. The bank does not keep only your balance; it keeps every deposit and withdrawal, and the balance is simply what you get when you add them up.
How it works
A command (PlaceOrder) is handled by an aggregate that checks its rules and, if they hold, emits events (OrderPlaced). With event sourcing, the events are what gets saved.
The aggregate current state is rebuilt by replaying its events through @EventSourcingHandler methods. Snapshots shortcut long histories.
Event handlers update separate read models (tables, search indexes) tailored to queries. The read side is eventually consistent with the write side.
Axon, Spring Modulith events and plain Kafka plus a database are common building blocks.
Going deeper
You gain a perfect audit trail, time travel and the ability to build new read models from history. You pay with complexity, versioning of old events (they live forever), and eventual consistency that users can notice.
Event handlers that update read models must be idempotent and replayable. A rebuild of a projection means replaying millions of events.
Do not use it everywhere. A CRUD admin screen gains nothing; use it for domains where history and audit are the business value.
Under the hood
The event store is an append-only log per aggregate id, protected by an optimistic version check (expected sequence number) so two commands cannot both append event number 7. Tracking event processors read the global stream with a stored token and push each event to handlers, which is how projections are fed.
Common mistakes
- Event sourcing for simple CRUD.
- Putting query joins in the write model.
- Changing past events instead of versioning them.
- Read-your-writes assumptions on an eventually consistent read model.
How Unravel recognises it
When you paste code, Unravel looks for these signs of cqrs and event sourcing. Spotting them yourself is a good habit when you read unfamiliar code:
- Annotations:
@CommandHandler@EventSourcingHandler@Aggregate@AggregateIdentifier@TargetAggregateIdentifier@QueryHandler@EventHandler@ProcessingGroup - Types:
CommandGatewayQueryGatewayEventStoreEventGatewayAggregateLifecycleEventBusCommandBusQueryBusEventStorageEngineEventSourcingRepositoryDomainEventDomainEventPublisher - Method calls:
AggregateLifecycle.apply()
Part of a learning path
Related concepts
Frequently asked questions
What is CQRS and event sourcing?
CQRS separates writing (commands that change state) from reading (queries over a shape built for the screen). Event sourcing goes further and stores the list of events that happened instead of the current state.
What are the most common mistakes with CQRS and event sourcing?
Event sourcing for simple CRUD. Putting query joins in the write model. Changing past events instead of versioning them. Read-your-writes assumptions on an eventually consistent read model.
How can I see CQRS and event sourcing in real code?
Paste your own Java into Unravel and it will point out where cqrs and event sourcing appears, explain how it works at the detail level you choose, and draw the flow of the code around it.