Exercise 08 · What is a promise to the client and what is a recalculation
Calculation V1 → V2 and determinism
- algorithm version
- determinismus
- replay
Principle
The instalment calculation evolves: V1 rounds the interest rate and truncates, V2 calculates precisely and adds insurance. The client received a promise based on V1.
When a replay recalculates an old calculation with new code, the past silently changes. That's why the event carries the result and the algorithm version (a fact), not just the inputs.
Replay must be deterministic: `apply` must not use randomness or the current time. A stream replayed twice yields the same checksum.
What to watch
- Promised vs. replayed instalment after switching to V2.
- The checksums of two replays differ with a non-deterministic apply.
Without the solution / With the solution
Without the solution
Run: deploy V2 without versioning
We complete a calculation in V1, then deploy V2. Replay recalculates the old calculation and apply generates the reference number randomly.
Run in the simulator without the solutionWith the solution
Run with a versioned result and a deterministic apply
The CalculationCompleted event carries the result and algorithm V1; replay returns the original promise. New calculations run on V2.
Run in the simulator with the solutionWhat to do: Click “Run: deploy V2 without versioning”. What to look at: The instalment after replay differs from the one we promised the client. 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.
Introduce a calculation algorithm version so that replaying history returns the original results and is deterministic.
# Task for a coding agent: Calculation V1 → V2 and determinism Stack: Java. Mode: guided. Goal: Introduce a calculation algorithm version so that replaying history returns the original results and is deterministic. ## 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 with `CalculationCompleted` and an upcast. If not, first create them in a minimal form. Read first, change nothing. Find and briefly summarise: - Where the instalment is calculated and how the result is stored. - All places in `apply`/replay that use time, randomness, UUIDs or external calls, and reads that recalculate the result (API, read models, e-mails). - Which of these parts already exist in the project and which are missing. Before you write your own infrastructure (broker client, outbox, retry), check whether the project or organisation already has it as a ready-made library, and use it. ## Expected behaviour A replay that recalculates a calculation promised under V1 using V2 code silently changes the past; so the event carries the result and the algorithm version, and replay is deterministic. Example: income CZK 40,600, price CZK 4,950,000, 20 % down payment, interest rate 3.72 %, 30 years: V1 instalment ratio 0.4500 (the 45 % limit passes), V2 0.4501 (rejected). ## Specific task Technical specifics (Java): `BigDecimal` with an explicit `RoundingMode`; algorithms behind a `PaymentCalculator` interface and a version map. Proceed step by step. After point 1 (plan), write what you found and what you propose, and wait for confirmation: 1. Plan: state which value is a fact (store it in the event) and which is derived (recalculate it). The completion result is a fact of the decision. 2. Add `algorithmVersion` and the calculated result to CalculationCompleted. New schema version: the upcast gives old events `algorithmVersion: 1`; if the state shape changes, bump the snapshot version. 3. Keep V1 as a separate implementation; V2 as a new one (calculation in hellers, rounding only at the end). Selection by the version in the event. 4. Move ID/time generation out of `apply` into the command, if it is there (the value goes into the event). 5. Determinism test: two replays of the same stream (different time, different storage, from a snapshot) have an identical state hash. 6. Golden test: a calculation from a V1 fixture still yields the same number even after V2 is deployed. The fixture has an input where V1 and V2 differ (example above). Watch out for: - Currency rounding (Decimal instead of double). - A rule change is not a bug fix: keep the history, record the fix as a new event. ## 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 - Golden tests for V1 and V2 (at least one input differs). - Replaying a V1 calculation returns the V1 result in the API and read models too. - The state hash of two replays is identical. - There is no time, randomness or I/O in `apply`. - 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: Calculation V1 → V2 and determinism Stack: Java. Mode: challenge. Goal: Introduce a calculation algorithm version so that replaying history returns the original results and is deterministic. ## 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 with `CalculationCompleted` and an upcast. If not, first create them in a minimal form. Read first, change nothing. Find and briefly summarise: - Where the instalment is calculated and how the result is stored. - All places in `apply`/replay that use time, randomness, UUIDs or external calls, and reads that recalculate the result (API, read models, e-mails). - Which of these parts already exist in the project and which are missing. Before you write your own infrastructure (broker client, outbox, retry), check whether the project or organisation already has it as a ready-made library, and use it. ## Expected behaviour A replay that recalculates a calculation promised under V1 using V2 code silently changes the past; so the event carries the result and the algorithm version, and replay is deterministic. Example: income CZK 40,600, price CZK 4,950,000, 20 % down payment, interest rate 3.72 %, 30 years: V1 instalment ratio 0.4500 (the 45 % limit passes), V2 0.4501 (rejected). ## Brief (Challenge) Deploy V2 of the instalment calculation so that historical calculations return their original result unchanged and replay is deterministic. Technical specifics (Java): `BigDecimal` with an explicit `RoundingMode`; algorithms behind a `PaymentCalculator` interface and a version map. 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 - Golden tests for V1 and V2 (at least one input differs). - Replaying a V1 calculation returns the V1 result in the API and read models too. - The state hash of two replays is identical. - There is no time, randomness or I/O in `apply`. - 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: Calculation V1 → V2 and determinism Stack: C#. Mode: guided. Goal: Introduce a calculation algorithm version so that replaying history returns the original results and is deterministic. ## 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 with `CalculationCompleted` and an upcast. If not, first create them in a minimal form. Read first, change nothing. Find and briefly summarise: - Where the instalment is calculated and how the result is stored. - All places in `apply`/replay that use time, randomness, UUIDs or external calls, and reads that recalculate the result (API, read models, e-mails). - Which of these parts already exist in the project and which are missing. Before you write your own infrastructure (broker client, outbox, retry), check whether the project or organisation already has it as a ready-made library, and use it. ## Expected behaviour A replay that recalculates a calculation promised under V1 using V2 code silently changes the past; so the event carries the result and the algorithm version, and replay is deterministic. Example: income CZK 40,600, price CZK 4,950,000, 20 % down payment, interest rate 3.72 %, 30 years: V1 instalment ratio 0.4500 (the 45 % limit passes), V2 0.4501 (rejected). ## Specific task Technical specifics (C#): `decimal` with `MidpointRounding`; algorithms behind an `IPaymentCalculator` interface, registered by version. Proceed step by step. After point 1 (plan), write what you found and what you propose, and wait for confirmation: 1. Plan: state which value is a fact (store it in the event) and which is derived (recalculate it). The completion result is a fact of the decision. 2. Add `algorithmVersion` and the calculated result to CalculationCompleted. New schema version: the upcast gives old events `algorithmVersion: 1`; if the state shape changes, bump the snapshot version. 3. Keep V1 as a separate implementation; V2 as a new one (calculation in hellers, rounding only at the end). Selection by the version in the event. 4. Move ID/time generation out of `apply` into the command, if it is there (the value goes into the event). 5. Determinism test: two replays of the same stream (different time, different storage, from a snapshot) have an identical state hash. 6. Golden test: a calculation from a V1 fixture still yields the same number even after V2 is deployed. The fixture has an input where V1 and V2 differ (example above). Watch out for: - Currency rounding (Decimal instead of double). - A rule change is not a bug fix: keep the history, record the fix as a new event. ## 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 - Golden tests for V1 and V2 (at least one input differs). - Replaying a V1 calculation returns the V1 result in the API and read models too. - The state hash of two replays is identical. - There is no time, randomness or I/O in `apply`. - 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: Calculation V1 → V2 and determinism Stack: C#. Mode: challenge. Goal: Introduce a calculation algorithm version so that replaying history returns the original results and is deterministic. ## 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 with `CalculationCompleted` and an upcast. If not, first create them in a minimal form. Read first, change nothing. Find and briefly summarise: - Where the instalment is calculated and how the result is stored. - All places in `apply`/replay that use time, randomness, UUIDs or external calls, and reads that recalculate the result (API, read models, e-mails). - Which of these parts already exist in the project and which are missing. Before you write your own infrastructure (broker client, outbox, retry), check whether the project or organisation already has it as a ready-made library, and use it. ## Expected behaviour A replay that recalculates a calculation promised under V1 using V2 code silently changes the past; so the event carries the result and the algorithm version, and replay is deterministic. Example: income CZK 40,600, price CZK 4,950,000, 20 % down payment, interest rate 3.72 %, 30 years: V1 instalment ratio 0.4500 (the 45 % limit passes), V2 0.4501 (rejected). ## Brief (Challenge) Deploy V2 of the instalment calculation so that historical calculations return their original result unchanged and replay is deterministic. Technical specifics (C#): `decimal` with `MidpointRounding`; algorithms behind an `IPaymentCalculator` interface, registered by version. 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 - Golden tests for V1 and V2 (at least one input differs). - Replaying a V1 calculation returns the V1 result in the API and read models too. - The state hash of two replays is identical. - There is no time, randomness or I/O in `apply`. - 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: Calculation V1 → V2 and determinism Stack: Web (HTML/JS or the app's stack). Mode: guided. Goal: Introduce a calculation algorithm version so that replaying history returns the original results and is deterministic. ## 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 with `CalculationCompleted` and an upcast. If not, first create them in a minimal form. Read first, change nothing. Find and briefly summarise: - Where the instalment is calculated and how the result is stored. - All places in `apply`/replay that use time, randomness, UUIDs or external calls, and reads that recalculate the result (API, read models, e-mails). - Which of these parts already exist in the project and which are missing. Before you write your own infrastructure (broker client, outbox, retry), check whether the project or organisation already has it as a ready-made library, and use it. ## Expected behaviour A replay that recalculates a calculation promised under V1 using V2 code silently changes the past; so the event carries the result and the algorithm version, and replay is deterministic. Example: income CZK 40,600, price CZK 4,950,000, 20 % down payment, interest rate 3.72 %, 30 years: V1 instalment ratio 0.4500 (the 45 % limit passes), V2 0.4501 (rejected). ## Specific task Technical specifics (Web (HTML/JS or the app's stack)): For money, use integers (hellers) or a decimal library; algorithm version as a key in a map of functions. Proceed step by step. After point 1 (plan), write what you found and what you propose, and wait for confirmation: 1. Plan: state which value is a fact (store it in the event) and which is derived (recalculate it). The completion result is a fact of the decision. 2. Add `algorithmVersion` and the calculated result to CalculationCompleted. New schema version: the upcast gives old events `algorithmVersion: 1`; if the state shape changes, bump the snapshot version. 3. Keep V1 as a separate implementation; V2 as a new one (calculation in hellers, rounding only at the end). Selection by the version in the event. 4. Move ID/time generation out of `apply` into the command, if it is there (the value goes into the event). 5. Determinism test: two replays of the same stream (different time, different storage, from a snapshot) have an identical state hash. 6. Golden test: a calculation from a V1 fixture still yields the same number even after V2 is deployed. The fixture has an input where V1 and V2 differ (example above). Watch out for: - Currency rounding (Decimal instead of double). - A rule change is not a bug fix: keep the history, record the fix as a new event. ## 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 - Golden tests for V1 and V2 (at least one input differs). - Replaying a V1 calculation returns the V1 result in the API and read models too. - The state hash of two replays is identical. - There is no time, randomness or I/O in `apply`. - 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: Calculation V1 → V2 and determinism Stack: Web (HTML/JS or the app's stack). Mode: challenge. Goal: Introduce a calculation algorithm version so that replaying history returns the original results and is deterministic. ## 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 with `CalculationCompleted` and an upcast. If not, first create them in a minimal form. Read first, change nothing. Find and briefly summarise: - Where the instalment is calculated and how the result is stored. - All places in `apply`/replay that use time, randomness, UUIDs or external calls, and reads that recalculate the result (API, read models, e-mails). - Which of these parts already exist in the project and which are missing. Before you write your own infrastructure (broker client, outbox, retry), check whether the project or organisation already has it as a ready-made library, and use it. ## Expected behaviour A replay that recalculates a calculation promised under V1 using V2 code silently changes the past; so the event carries the result and the algorithm version, and replay is deterministic. Example: income CZK 40,600, price CZK 4,950,000, 20 % down payment, interest rate 3.72 %, 30 years: V1 instalment ratio 0.4500 (the 45 % limit passes), V2 0.4501 (rejected). ## Brief (Challenge) Deploy V2 of the instalment calculation so that historical calculations return their original result unchanged and replay is deterministic. Technical specifics (Web (HTML/JS or the app's stack)): For money, use integers (hellers) or a decimal library; algorithm version as a key in a map of functions. 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 - Golden tests for V1 and V2 (at least one input differs). - Replaying a V1 calculation returns the V1 result in the API and read models too. - The state hash of two replays is identical. - There is no time, randomness or I/O in `apply`. - 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