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.

  1. Riskposuzuje
  2. Approvalwaiting
  3. Identitynot yet
  4. Client“Where is my application?”

Reading from the write model

  1. Query
  2. Replaying 251 events
  3. 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.

More on this topic

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.

  1. CommandChangeIncome
  2. Write modelenforces rules
  3. EventIncomeChanged
  4. Projectionbuilds the view
  5. Read modelshape for the query
  6. QueryGetCalculation

What flows where

  1. A command returns only an acknowledgement and a version
  2. A query changes nothing

TakeawayCommand → Write model → Event → Projection → Read model → Query.

More on this topic

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

  1. Commandincome 95 000 → v252
  2. Query immediatelyreads v251
  3. The user does not see their change

With read-your-writes

  1. Commandreturns v252
  2. Query minVersion=252waits for the projection
  3. Current data

CQRS without Event Sourcing

  1. Command
  2. Tablewrite model
  3. CalculationChangedevent on write
  4. 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.

More on this topic

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