Part 06 of 16 · Day 1 · CQRS, projections
State scattered across services
Problem and experience
The client does not know the state of the application
The answer to “Where is my application?” is scattered across several services.
- Riskposuzuje
- Approvalwaiting
- Identitynot yet
- Client“Where is my application?”
Reading from the write model
- Query
- Replaying 251 events
- Answer
From practiceHow long does it take you to tell a client what state their application is in?
TakeawayWhat stores changes well does not necessarily answer questions well.
What it means
The write model enforces the rules. Reading needs a different shape: a summary, a list, a status for the client.
Why we use it
When we squeeze both jobs into one model, each one makes compromises.
When it makes sense
When reading and writing really differ: data shape, load, availability.
Where it breaks
- Reading from the event store by replaying for every query.
- Doing CQRS for simple CRUD where reading and writing do not differ.
Imagine
The adviser opens a calculation. The system replays 251 events to show one screen.
It is not about having two models. It is about each one doing just one job.
Principle, application and exercise
A command changes, a query reads
Writing and reading have their own models. They are connected by an event and a projection.
- CommandChangeIncome
- Write modelenforces rules
- EventIncomeChanged
- Projectionbuilds the view
- Read modelshape for the query
- QueryGetCalculation
What flows where
- A command returns only an acknowledgement and a version
- A query changes nothing
TakeawayCommand → Write model → Event → Projection → Read model → Query.
What it means
CQRS (Command Query Responsibility Segregation) separates two jobs: changing state and reading state. The read model (projection) is built from events.
Why we use it
The read model is designed around what the screen needs. The write model stays clean.
When it makes sense
When read and write needs differ, or when reads need to scale separately.
Where it breaks
- A command must not read from the read model to decide on a write.
- A command that returns the whole read model joins both sides together again.
Imagine
An income change goes through a command. The calculation screen reads a projection that already has the instalment and DTI computed.
The link between the two sides is an event, not a shared table.
Principle, application and exercise
The read model lags briefly
After the command, the projection has not yet processed the change. The lag can be measured and handled with a version token. CQRS is not Event Sourcing.
Unhandled
- Commandincome 95 000 → v252
- Query immediatelyreads v251
- The user does not see their change
With read-your-writes
- Commandreturns v252
- Query minVersion=252waits for the projection
- Current data
CQRS without Event Sourcing
- Command
- Tablewrite model
- CalculationChangedevent on write
- Read model
Trade-offMore models and eventual consistency: reads lag behind and the UI has to account for it. CQRS does not necessarily speed things up, it only separates.
TakeawayLag is not a bug. The bug is when the UI does not account for it. And CQRS can be used with or without Event Sourcing.
What it means
Eventual consistency: the read model catches up with the write, but not instantly. We measure the lag as a number of versions and milliseconds. The command returns a version number, and a query with minVersion either waits or returns a flag that the data is stale.
Why we use it
Otherwise, after saving, the user sees the old number and thinks the change failed.
When it makes sense
Always, when the read is not in the same transaction as the write.
Where it breaks
- Waiting without a limit. The query must have a timeout.
- Showing stale data without a warning.
- Not measuring projection lag.
- Confusing CQRS with Event Sourcing.
Imagine
The adviser changes the income and immediately loads the calculation. Without handling, they see the old instalment. In the lab, switch to “Write model without Event Sourcing”: the flow and the lag stay the same.
The price of separation is time. We pay it knowingly, not by surprise.
Training for your team
Would you like this training for your team?
We run the training on your code and your domain problem. We agree the scope, dates and format with you.
Discuss the training →