Exercise 03 · Read model from events, fixing a bug by recomputation
Projections and replay
- projection
- replay
- side effects
Principle
A projection is a read model derived from events (here a calculation summary: number of income and interest rate changes, latest income).
A bug in a projection is not a bug in the history. We fix the projection code and replay the events again (rebuild).
Beware of side effects: a replay must not resend an e-mail or take a payment again. Rebuilding a view is separate from executing real operations.
What to watch
- The number of income changes in projection v1 does not match the number of IncomeChanged events.
- A rebuild without suppressing effects sends the client the e-mail again (right: “Cause → Effect”).
Without the solution / With the solution
Without the solution
Run the faulty projection v1
Projection v1 counts an interest rate change as an income change. After the fix we run a rebuild, but the replay does not suppress side effects.
Run in the simulator without the solutionWith the solution
Run the fix: projection v2 + replay without effects
Projection v2 counts correctly; the rebuild runs in a no-side-effects mode. History stayed unchanged; only the view was re-derived.
Run in the simulator with the solutionWhat to do: Click “Run faulty projection v1”. What to look at: After the projection rebuild, the client receives the e-mail a second time. The simulator needs JavaScript and runs as a separate application.
Try it in your own project
Copy the prompt into your coding agent (Claude Code, Copilot, Codex…) and run it in your project's repository.
Add a projection over the calculator event stream and be able to rebuild it at any time without repeating side effects.
# Task for a coding agent: Projections and replay
Stack: Java. Mode: guided.
Goal: Add a projection over the calculator event stream and be able to rebuild it at any time without repeating side effects.
## Context of the existing code
You are working in an existing project (Java backend (typically Java 21, Spring Boot, JPA or JDBC). Use the build tool and style the project already has (Maven/Gradle, JUnit 5).).
Domain: a mortgage calculator that is gradually turning into an event-driven system (calculator, risk, approval, identity, account, documents, payout).
Prerequisite: the project has an event store for calculations (Event Sourcing). If not, first create a minimal one: events `{ streamId, version, type, payload }`, append-only.
Read first, change nothing. Find and briefly summarise:
- Where summaries/reports are currently produced from calculation data.
- Code that sends an e-mail or another side effect when a calculation is completed. If there is none, propose an e-mail on `CalculationCompleted` via an in-memory `Mailer`.
- The event store (or the stream that replaces it). If it has no global write order across streams (autoincrement `position`), add one; time is not enough.
- Which of these parts already exist in the project and which are missing. Before writing your own infrastructure (projections, checkpoint), check whether the project or organisation already has it as a ready-made library, and use it.
## Expected behaviour
A projection is a read model derived from events, here a calculation summary (number of income and interest rate changes, latest income). A bug in a projection is not a bug in the history: you fix the code and replay the events (rebuild) without repeating side effects. Example: projection v1 counts an interest rate change as an income change; after the fix and rebuild, the number of income changes matches the number of `IncomeChanged` events and the completion e-mail is not sent a second time.
## Specific task
Technical specifics (Java): Keep the projection in a separate table, processing as a `@Scheduled` poller or a listener over the event store with a stored offset.
Proceed step by step. After point 1 (plan), write what you found and what you propose, and wait for confirmation:
1. Plan: describe the projection (fields, which events change it) and where it will be stored. Default proposal: the summary from “Expected behaviour”.
2. Implement the projection handler as a pure function `(model, event) -> model` and store the position of the last processed event.
3. Add a `rebuild` command that deletes the projection and replays it from the beginning.
4. Separate side effects: the effects handler does not run in `replay` mode (an explicit flag, not commented-out code). Derive the mode from `effectsPosition`, which rebuild does not delete, so that an event without a sent e-mail goes through as `live`.
5. Test: the faulty projection version returns the wrong number, the fixed one returns the right one after a rebuild; no e-mail is sent during the rebuild.
Watch out for:
- Rebuild while running: what happens to events that arrive during the replay, and what does a reader see in the meantime?
- Idempotency of both handlers (projection and e-mail) when the same event is processed again.
## What you must not change
- The existing public API and the behaviour of the calculator endpoints that currently work.
- The database schema of existing tables (new tables/migrations are fine; renaming and deleting are not).
- Other people's tests, seed data and existing values. If a change is necessary, put it in a separate commit and state it. If a change to an event's shape or a new consumer necessarily changes an existing test, update it in a separate commit and explain why in the summary.
- Public contracts (APIs, event schemas) without backward compatibility.
## Done when
- The projection is purely derived; after deletion and rebuild it is identical.
- A rebuild sends no e-mail and calls no external service.
- An event that arrives during a rebuild is in the projection and its e-mail was sent exactly once.
- The processing position is stored; the run resumes where it left off.
- JUnit 5 tests in the existing `src/test/java` structure pass with the project's build tool.
- In the summary, state: what you changed, what you assumed, what remains.# Task for a coding agent: Projections and replay
Stack: Java. Mode: challenge.
Goal: Add a projection over the calculator event stream and be able to rebuild it at any time without repeating side effects.
## Context of the existing code
You are working in an existing project (Java backend (typically Java 21, Spring Boot, JPA or JDBC). Use the build tool and style the project already has (Maven/Gradle, JUnit 5).).
Domain: a mortgage calculator that is gradually turning into an event-driven system (calculator, risk, approval, identity, account, documents, payout).
Prerequisite: the project has an event store for calculations (Event Sourcing). If not, first create a minimal one: events `{ streamId, version, type, payload }`, append-only.
Read first, change nothing. Find and briefly summarise:
- Where summaries/reports are currently produced from calculation data.
- Code that sends an e-mail or another side effect when a calculation is completed. If there is none, propose an e-mail on `CalculationCompleted` via an in-memory `Mailer`.
- The event store (or the stream that replaces it). If it has no global write order across streams (autoincrement `position`), add one; time is not enough.
- Which of these parts already exist in the project and which are missing. Before writing your own infrastructure (projections, checkpoint), check whether the project or organisation already has it as a ready-made library, and use it.
## Expected behaviour
A projection is a read model derived from events, here a calculation summary (number of income and interest rate changes, latest income). A bug in a projection is not a bug in the history: you fix the code and replay the events (rebuild) without repeating side effects. Example: projection v1 counts an interest rate change as an income change; after the fix and rebuild, the number of income changes matches the number of `IncomeChanged` events and the completion e-mail is not sent a second time.
## Brief (Challenge)
Add a rebuildable calculation summary projection and prove with a test that a rebuild fixes the bug and triggers no side effect.
Technical specifics (Java): Keep the projection in a separate table, processing as a `@Scheduled` poller or a listener over the event store with a stored offset.
Choose your own approach. The pitfalls in this topic are not listed: find them (concurrency, redelivery, ordering, failure midway) and for each write a test that would expose it. At the end, list which pitfalls you found and which you deliberately left open.
## What you must not change
- The existing public API and the behaviour of the calculator endpoints that currently work.
- The database schema of existing tables (new tables/migrations are fine; renaming and deleting are not).
- Other people's tests, seed data and existing values. If a change is necessary, put it in a separate commit and state it. If a change to an event's shape or a new consumer necessarily changes an existing test, update it in a separate commit and explain why in the summary.
- Public contracts (APIs, event schemas) without backward compatibility.
## Done when
- The projection is purely derived; after deletion and rebuild it is identical.
- A rebuild sends no e-mail and calls no external service.
- An event that arrives during a rebuild is in the projection and its e-mail was sent exactly once.
- The processing position is stored; the run resumes where it left off.
- JUnit 5 tests in the existing `src/test/java` structure pass with the project's build tool.
- In the summary, state: what you changed, what you assumed, what remains.# Task for a coding agent: Projections and replay
Stack: C#. Mode: guided.
Goal: Add a projection over the calculator event stream and be able to rebuild it at any time without repeating side effects.
## Context of the existing code
You are working in an existing project (C# backend (.NET, ASP.NET Core, EF Core or Dapper). Use the solution, style and DI conventions the project already has (xUnit/NUnit).).
Domain: a mortgage calculator that is gradually turning into an event-driven system (calculator, risk, approval, identity, account, documents, payout).
Prerequisite: the project has an event store for calculations (Event Sourcing). If not, first create a minimal one: events `{ streamId, version, type, payload }`, append-only.
Read first, change nothing. Find and briefly summarise:
- Where summaries/reports are currently produced from calculation data.
- Code that sends an e-mail or another side effect when a calculation is completed. If there is none, propose an e-mail on `CalculationCompleted` via an in-memory `Mailer`.
- The event store (or the stream that replaces it). If it has no global write order across streams (autoincrement `position`), add one; time is not enough.
- Which of these parts already exist in the project and which are missing. Before writing your own infrastructure (projections, checkpoint), check whether the project or organisation already has it as a ready-made library, and use it.
## Expected behaviour
A projection is a read model derived from events, here a calculation summary (number of income and interest rate changes, latest income). A bug in a projection is not a bug in the history: you fix the code and replay the events (rebuild) without repeating side effects. Example: projection v1 counts an interest rate change as an income change; after the fix and rebuild, the number of income changes matches the number of `IncomeChanged` events and the completion e-mail is not sent a second time.
## Specific task
Technical specifics (C#): Projection as a `BackgroundService` reading the event store from the stored checkpoint; effects behind a separate interface that is not used during rebuild.
Proceed step by step. After point 1 (plan), write what you found and what you propose, and wait for confirmation:
1. Plan: describe the projection (fields, which events change it) and where it will be stored. Default proposal: the summary from “Expected behaviour”.
2. Implement the projection handler as a pure function `(model, event) -> model` and store the position of the last processed event.
3. Add a `rebuild` command that deletes the projection and replays it from the beginning.
4. Separate side effects: the effects handler does not run in `replay` mode (an explicit flag, not commented-out code). Derive the mode from `effectsPosition`, which rebuild does not delete, so that an event without a sent e-mail goes through as `live`.
5. Test: the faulty projection version returns the wrong number, the fixed one returns the right one after a rebuild; no e-mail is sent during the rebuild.
Watch out for:
- Rebuild while running: what happens to events that arrive during the replay, and what does a reader see in the meantime?
- Idempotency of both handlers (projection and e-mail) when the same event is processed again.
## What you must not change
- The existing public API and the behaviour of the calculator endpoints that currently work.
- The database schema of existing tables (new tables/migrations are fine; renaming and deleting are not).
- Other people's tests, seed data and existing values. If a change is necessary, put it in a separate commit and state it. If a change to an event's shape or a new consumer necessarily changes an existing test, update it in a separate commit and explain why in the summary.
- Public contracts (APIs, event schemas) without backward compatibility.
## Done when
- The projection is purely derived; after deletion and rebuild it is identical.
- A rebuild sends no e-mail and calls no external service.
- An event that arrives during a rebuild is in the projection and its e-mail was sent exactly once.
- The processing position is stored; the run resumes where it left off.
- Tests in the existing test project pass (`dotnet test`).
- In the summary, state: what you changed, what you assumed, what remains.# Task for a coding agent: Projections and replay
Stack: C#. Mode: challenge.
Goal: Add a projection over the calculator event stream and be able to rebuild it at any time without repeating side effects.
## Context of the existing code
You are working in an existing project (C# backend (.NET, ASP.NET Core, EF Core or Dapper). Use the solution, style and DI conventions the project already has (xUnit/NUnit).).
Domain: a mortgage calculator that is gradually turning into an event-driven system (calculator, risk, approval, identity, account, documents, payout).
Prerequisite: the project has an event store for calculations (Event Sourcing). If not, first create a minimal one: events `{ streamId, version, type, payload }`, append-only.
Read first, change nothing. Find and briefly summarise:
- Where summaries/reports are currently produced from calculation data.
- Code that sends an e-mail or another side effect when a calculation is completed. If there is none, propose an e-mail on `CalculationCompleted` via an in-memory `Mailer`.
- The event store (or the stream that replaces it). If it has no global write order across streams (autoincrement `position`), add one; time is not enough.
- Which of these parts already exist in the project and which are missing. Before writing your own infrastructure (projections, checkpoint), check whether the project or organisation already has it as a ready-made library, and use it.
## Expected behaviour
A projection is a read model derived from events, here a calculation summary (number of income and interest rate changes, latest income). A bug in a projection is not a bug in the history: you fix the code and replay the events (rebuild) without repeating side effects. Example: projection v1 counts an interest rate change as an income change; after the fix and rebuild, the number of income changes matches the number of `IncomeChanged` events and the completion e-mail is not sent a second time.
## Brief (Challenge)
Add a rebuildable calculation summary projection and prove with a test that a rebuild fixes the bug and triggers no side effect.
Technical specifics (C#): Projection as a `BackgroundService` reading the event store from the stored checkpoint; effects behind a separate interface that is not used during rebuild.
Choose your own approach. The pitfalls in this topic are not listed: find them (concurrency, redelivery, ordering, failure midway) and for each write a test that would expose it. At the end, list which pitfalls you found and which you deliberately left open.
## What you must not change
- The existing public API and the behaviour of the calculator endpoints that currently work.
- The database schema of existing tables (new tables/migrations are fine; renaming and deleting are not).
- Other people's tests, seed data and existing values. If a change is necessary, put it in a separate commit and state it. If a change to an event's shape or a new consumer necessarily changes an existing test, update it in a separate commit and explain why in the summary.
- Public contracts (APIs, event schemas) without backward compatibility.
## Done when
- The projection is purely derived; after deletion and rebuild it is identical.
- A rebuild sends no e-mail and calls no external service.
- An event that arrives during a rebuild is in the projection and its e-mail was sent exactly once.
- The processing position is stored; the run resumes where it left off.
- Tests in the existing test project pass (`dotnet test`).
- In the summary, state: what you changed, what you assumed, what remains.# Task for a coding agent: Projections and replay
Stack: Web (HTML/JS or the app's stack). Mode: guided.
Goal: Add a projection over the calculator event stream and be able to rebuild it at any time without repeating side effects.
## Context of the existing code
You are working in an existing project (web application: a browser frontend, possibly with its own backend. If the project has a backend, the event store, broker and services belong on the backend, and the points about endpoints and tables refer to it. If it has no backend, use IndexedDB in the browser and `fake-indexeddb` in tests. Keep the project's framework and test runner; add new dependencies only with a justification.).
Domain: a mortgage calculator that is gradually turning into an event-driven system (calculator, risk, approval, identity, account, documents, payout).
Prerequisite: the project has an event store for calculations (Event Sourcing). If not, first create a minimal one: events `{ streamId, version, type, payload }`, append-only.
Read first, change nothing. Find and briefly summarise:
- Where summaries/reports are currently produced from calculation data.
- Code that sends an e-mail or another side effect when a calculation is completed. If there is none, propose an e-mail on `CalculationCompleted` via an in-memory `Mailer`.
- The event store (or the stream that replaces it). If it has no global write order across streams (autoincrement `position`), add one; time is not enough.
- Which of these parts already exist in the project and which are missing. Before writing your own infrastructure (projections, checkpoint), check whether the project or organisation already has it as a ready-made library, and use it.
## Expected behaviour
A projection is a read model derived from events, here a calculation summary (number of income and interest rate changes, latest income). A bug in a projection is not a bug in the history: you fix the code and replay the events (rebuild) without repeating side effects. Example: projection v1 counts an interest rate change as an income change; after the fix and rebuild, the number of income changes matches the number of `IncomeChanged` events and the completion e-mail is not sent a second time.
## Specific task
Technical specifics (Web (HTML/JS or the app's stack)): Projection as a reducer over events in `position` order; store the read model and checkpoint (`position`, `effectsPosition`) in one transaction, the checkpoint separately.
Proceed step by step. After point 1 (plan), write what you found and what you propose, and wait for confirmation:
1. Plan: describe the projection (fields, which events change it) and where it will be stored. Default proposal: the summary from “Expected behaviour”.
2. Implement the projection handler as a pure function `(model, event) -> model` and store the position of the last processed event.
3. Add a `rebuild` command that deletes the projection and replays it from the beginning.
4. Separate side effects: the effects handler does not run in `replay` mode (an explicit flag, not commented-out code). Derive the mode from `effectsPosition`, which rebuild does not delete, so that an event without a sent e-mail goes through as `live`.
5. Test: the faulty projection version returns the wrong number, the fixed one returns the right one after a rebuild; no e-mail is sent during the rebuild.
Watch out for:
- Rebuild while running: what happens to events that arrive during the replay, and what does a reader see in the meantime?
- Idempotency of both handlers (projection and e-mail) when the same event is processed again.
## What you must not change
- The existing public API and the behaviour of the calculator endpoints that currently work.
- The database schema of existing tables (new tables/migrations are fine; renaming and deleting are not).
- Other people's tests, seed data and existing values. If a change is necessary, put it in a separate commit and state it. If a change to an event's shape or a new consumer necessarily changes an existing test, update it in a separate commit and explain why in the summary.
- Public contracts (APIs, event schemas) without backward compatibility.
## Done when
- The projection is purely derived; after deletion and rebuild it is identical.
- A rebuild sends no e-mail and calls no external service.
- An event that arrives during a rebuild is in the projection and its e-mail was sent exactly once.
- The processing position is stored; the run resumes where it left off.
- Tests pass with the existing test runner; lint and build pass if the project has them.
- In the summary, state: what you changed, what you assumed, what remains.# Task for a coding agent: Projections and replay
Stack: Web (HTML/JS or the app's stack). Mode: challenge.
Goal: Add a projection over the calculator event stream and be able to rebuild it at any time without repeating side effects.
## Context of the existing code
You are working in an existing project (web application: a browser frontend, possibly with its own backend. If the project has a backend, the event store, broker and services belong on the backend, and the points about endpoints and tables refer to it. If it has no backend, use IndexedDB in the browser and `fake-indexeddb` in tests. Keep the project's framework and test runner; add new dependencies only with a justification.).
Domain: a mortgage calculator that is gradually turning into an event-driven system (calculator, risk, approval, identity, account, documents, payout).
Prerequisite: the project has an event store for calculations (Event Sourcing). If not, first create a minimal one: events `{ streamId, version, type, payload }`, append-only.
Read first, change nothing. Find and briefly summarise:
- Where summaries/reports are currently produced from calculation data.
- Code that sends an e-mail or another side effect when a calculation is completed. If there is none, propose an e-mail on `CalculationCompleted` via an in-memory `Mailer`.
- The event store (or the stream that replaces it). If it has no global write order across streams (autoincrement `position`), add one; time is not enough.
- Which of these parts already exist in the project and which are missing. Before writing your own infrastructure (projections, checkpoint), check whether the project or organisation already has it as a ready-made library, and use it.
## Expected behaviour
A projection is a read model derived from events, here a calculation summary (number of income and interest rate changes, latest income). A bug in a projection is not a bug in the history: you fix the code and replay the events (rebuild) without repeating side effects. Example: projection v1 counts an interest rate change as an income change; after the fix and rebuild, the number of income changes matches the number of `IncomeChanged` events and the completion e-mail is not sent a second time.
## Brief (Challenge)
Add a rebuildable calculation summary projection and prove with a test that a rebuild fixes the bug and triggers no side effect.
Technical specifics (Web (HTML/JS or the app's stack)): Projection as a reducer over events in `position` order; store the read model and checkpoint (`position`, `effectsPosition`) in one transaction, the checkpoint separately.
Choose your own approach. The pitfalls in this topic are not listed: find them (concurrency, redelivery, ordering, failure midway) and for each write a test that would expose it. At the end, list which pitfalls you found and which you deliberately left open.
## What you must not change
- The existing public API and the behaviour of the calculator endpoints that currently work.
- The database schema of existing tables (new tables/migrations are fine; renaming and deleting are not).
- Other people's tests, seed data and existing values. If a change is necessary, put it in a separate commit and state it. If a change to an event's shape or a new consumer necessarily changes an existing test, update it in a separate commit and explain why in the summary.
- Public contracts (APIs, event schemas) without backward compatibility.
## Done when
- The projection is purely derived; after deletion and rebuild it is identical.
- A rebuild sends no e-mail and calls no external service.
- An event that arrives during a rebuild is in the projection and its e-mail was sent exactly once.
- The processing position is stored; the run resumes where it left off.
- Tests pass with the existing test runner; lint and build pass if the project has them.
- In the summary, state: what you changed, what you assumed, what remains.Related parts of the presentation
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