Cvičení 04 · Command → Write model → Event → Projection (Read model) → Query

CQRS: příkazy a dotazy

  • CQRS
  • write/read model
  • eventual consistency
  • zpoždění projekce
  • CQRS ≠ Event Sourcing

Princip

CQRS (Command Query Responsibility Segregation) odděluje dvě různé práce. Příkaz (Command) mění stav a hlídá pravidla. Dotaz (Query) jen čte a nic nemění. Každá strana má vlastní model, který se může lišit tvarem i úložištěm.

Tok: Command → Write model (změna stavu) → Event → Projection (Read model) → Query. Událost je spojnice: zápisová strana ji zveřejní a projekce z ní průběžně staví čtecí model přesně v takovém tvaru, jaký dotaz potřebuje.

Cena je eventual consistency: čtecí model je vždy trochu pozadu. Zpoždění projekce se dá měřit (verze pozadu, milisekundy) a ošetřit, například tokenem verze z příkazu (read-your-writes).

CQRS ≠ Event Sourcing. CQRS je o oddělení zápisu a čtení. Event Sourcing je o tom, jak se ukládá zápisová strana (jako události místo přepsaného řádku). Jde je kombinovat, ale jedno nevyžaduje druhé: CQRS funguje i nad obyčejnou tabulkou (přepínač „Zápisový model bez Event Sourcingu“). A Event Sourcing se obejde bez CQRS, pokud čteš přímo přehráním streamu, což je právě ten problém tohoto kroku.

Co sledovat

  • Bez CQRS: každý dotaz přehraje celý stream (cena „přečteno Nד). S CQRS: dotaz čte hotový pohled (1×).
  • Hned po příkazu je čtecí model pozadu. Panel ukazuje verze pozadu, milisekundy zpoždění a graf.
  • Se zapnutým read-your-writes dotaz počká na verzi z příkazu a vrátí aktuální data.
  • Přepni zápisový model na tabulku: tok a zpoždění zůstanou stejné, jen zdroj pravdy není stream.

Bez řešení / S řešením

Bez řešení

Spustit bez CQRS (čtení ze zápisového modelu)

Poradce má za sebou 250 změn. Každý dotaz na kalkulaci jde do zápisového modelu a přehraje celý stream. Čtení i zápis sdílejí jednu strukturu.

Spustit v simulátoru bez řešení

S řešením

Spustit s CQRS a read-your-writes

Stejných 250 změn, ale dotazy čte samostatný čtecí model: levné (1×) a nezávislé na zápisu. Čtecí model je krátce pozadu, to je vidět na zpoždění. Dotaz po příkazu nese token verze a počká na projekci, takže uživatel uvidí vlastní změnu.

Spustit v simulátoru s řešením

Co udělat: Klikni na „Spustit bez CQRS (čtení ze zápisového modelu)“. Na co se dívat: Každý dotaz přečetl celý stream (v panelu Dotazy: přečteno 251×). Simulátor potřebuje JavaScript a běží jako samostatná aplikace.

Zkus to ve svém projektu

Zkopíruj prompt do svého coding agenta (Claude Code, Copilot, Codex…) a spusť ho v repozitáři svého projektu.

Oddělit zápis a čtení kalkulačky: příkazy mění zápisový model, dotazy čtou samostatný čtecí model, a změřit zpoždění mezi nimi.

Jazyk
Varianta
# Úkol pro coding agenta: CQRS: příkazy a dotazy

Stack: Java. Režim: s vedením.
Cíl: Oddělit zápis a čtení kalkulačky: příkazy mění zápisový model, dotazy čtou samostatný čtecí model, a změřit zpoždění mezi nimi.

## Kontext existujícího kódu

Pracuješ v existujícím projektu (Java backend (typicky Java 21, Spring Boot, JPA nebo JDBC). Použij build nástroj a styl, který projekt už má (Maven/Gradle, JUnit 5).).
Doména: hypoteční kalkulačka, která se postupně mění v event-driven systém (kalkulačka, rizika, schvalování, identita, účet, dokumenty, výplata).
Předpoklad: projekt má event store výpočtů (Event Sourcing) a projekci s checkpointem, kterou jde zastavit a spustit. Pokud ne, nejdřív založ minimální: události s globální pozicí a odběr, který z nich staví čtecí model a ukládá pozici.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Endpointy kalkulačky, které míchají změnu stavu a čtení v jedné službě nebo jednom modelu (DTO, entita, repozitář).
- Dotazy, které dnes skládají pohled z více míst, přehrávají historii nebo dělají drahé dotazy do zápisové tabulky.
- Existující mechanismus publikace změn (události, outbox, broker), který lze použít jako spojnici mezi stranami.
- Místa, kde UI čte data hned po uložení (read-your-writes).
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (čtecí model, čekání na verzi, metriky), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

## Očekávané chování

Příkaz mění stav a hlídá pravidla, dotaz jen čte; každá strana má vlastní model a spojnicí je událost. Čtecí model je chvíli pozadu (eventual consistency): zpoždění jde měřit a dotaz s verzí z příkazu na projekci počká (read-your-writes). CQRS nevyžaduje Event Sourcing. Příklad: výpočet s 250 změnami; bez CQRS každý dotaz přehraje 250 událostí, s CQRS přečte jeden řádek čtecího modelu.

## Konkrétní úkol

Technická specifika (Java): Příkazy a dotazy jako oddělené handlery (např. `CommandHandler` / `QueryHandler`); čtecí model v samostatné tabulce nebo indexu plněný listenerem událostí. Nad Spring Eventing/Kafka listenerem drž offset.

Postupuj po krocích. Po bodu 1 (plán) napiš, co zjistíš a co navrhuješ, a počkej na potvrzení:
1. Plán: vypiš příkazy (mění stav, vrací jen potvrzení a verzi) a dotazy (nic nemění). Nakresli tok Command → Write model → Event → Projection → Query.
2. Zaveď oddělené modely: zápisový model drží pravidla (invarianty), čtecí model má tvar přesně podle dotazu. Příkazy nevracejí čtecí model. Stávající endpointy, které po zápisu vracejí stav, nech; příkazy a dotazy přidej jako nové (např. `/api/v2/commands/…`, `/api/v2/queries/…`).
3. Změnu zveřejni jako událost (v jedné transakci se zápisem) a z ní staví projekce čtecí model. S event storem je událost sám zápis; variantu s obyčejnou tabulkou (řádek + outbox v jedné transakci) jen popiš v dokumentaci.
4. Příkaz vrací verzi. Dotaz přijme `minVersion` a počká nejvýš 1 s (konfigurovatelně), pak vrátí poslední stav se `stale: true`. Bez `minVersion` vrátí hned, co čtecí model má.
5. Změř zpoždění projekce: počet událostí pozadu (poslední pozice v event store − checkpoint) a stáří nejstarší nezpracované události. Vystav je jako metriku, např. `GET /metrics` ve formátu Prometheus. Když event store neumí vrátit poslední pozici, doplň to.
6. Test: po příkazu dotaz s tokenem vrátí novou verzi; bez tokenu smí vrátit starší; při zastavené projekci (`stop()` odběru) zápis funguje dál a po `start()` se čtecí model dožene.

Na co si dát pozor:
- Nezaměňuj CQRS s Event Sourcingem: oddělení modelů nevyžaduje události jako úložiště.
- Nedělej CQRS všude: pro jednoduché CRUD obrazovky přidá jen složitost. V kalkulačce se vyplatí pro detail výpočtu ze streamu a pro souhrny; CRUD formulář nech.
- UI po uložení nesmí předpokládat, že čtecí model je hned aktuální (token verze, optimistické zobrazení).
- Příkaz nesmí číst z čtecího modelu, aby rozhodl o zápisu: rozhodnutí patří zápisové straně.

## Co nesmíš měnit

- Stávající veřejné API a chování endpointů kalkulačky, které teď funguje.
- Databázové schéma stávajících tabulek (nové tabulky/migrace jsou v pořádku, přejmenování a mazání ne).
- Cizí testy, seed data a existující hodnoty. Když je změna nutná, dej ji do samostatného commitu a uveď ji. Pokud změna tvaru události nebo nový konzument nutně mění existující test, uprav ho v samostatném commitu a v závěru uveď proč.
- Veřejné kontrakty (API, schémata událostí) bez zpětné kompatibility.

## Hotovo, když

- Příkazy nevracejí čtecí model, dotazy nic nemění; stávající endpointy vracejí stejné odpovědi jako dřív.
- Čtecí model lze smazat a přestavět z událostí nebo ze zápisové tabulky.
- Existuje test pro zastaralé čtení, pro read-your-writes a pro výpadek projekce.
- Zpoždění projekce je měřitelné.
- Dokumentace uvádí, že CQRS nevyžaduje Event Sourcing, a volba zápisové strany je zdůvodněná.
- JUnit 5 testy ve stávající struktuře `src/test/java` projdou build nástrojem projektu.
- V závěru uveď: co jsi změnil, co jsi předpokládal, co zbývá.
# Úkol pro coding agenta: CQRS: příkazy a dotazy

Stack: Java. Režim: challenge.
Cíl: Oddělit zápis a čtení kalkulačky: příkazy mění zápisový model, dotazy čtou samostatný čtecí model, a změřit zpoždění mezi nimi.

## Kontext existujícího kódu

Pracuješ v existujícím projektu (Java backend (typicky Java 21, Spring Boot, JPA nebo JDBC). Použij build nástroj a styl, který projekt už má (Maven/Gradle, JUnit 5).).
Doména: hypoteční kalkulačka, která se postupně mění v event-driven systém (kalkulačka, rizika, schvalování, identita, účet, dokumenty, výplata).
Předpoklad: projekt má event store výpočtů (Event Sourcing) a projekci s checkpointem, kterou jde zastavit a spustit. Pokud ne, nejdřív založ minimální: události s globální pozicí a odběr, který z nich staví čtecí model a ukládá pozici.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Endpointy kalkulačky, které míchají změnu stavu a čtení v jedné službě nebo jednom modelu (DTO, entita, repozitář).
- Dotazy, které dnes skládají pohled z více míst, přehrávají historii nebo dělají drahé dotazy do zápisové tabulky.
- Existující mechanismus publikace změn (události, outbox, broker), který lze použít jako spojnici mezi stranami.
- Místa, kde UI čte data hned po uložení (read-your-writes).
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (čtecí model, čekání na verzi, metriky), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

## Očekávané chování

Příkaz mění stav a hlídá pravidla, dotaz jen čte; každá strana má vlastní model a spojnicí je událost. Čtecí model je chvíli pozadu (eventual consistency): zpoždění jde měřit a dotaz s verzí z příkazu na projekci počká (read-your-writes). CQRS nevyžaduje Event Sourcing. Příklad: výpočet s 250 změnami; bez CQRS každý dotaz přehraje 250 událostí, s CQRS přečte jeden řádek čtecího modelu.

## Zadání (Challenge)

Přidej vedle stávajícího endpointu oddělený čtecí model kalkulačky stavěný z událostí, s tokenem verze pro read-your-writes a metrikou zpoždění projekce. Stávající endpoint nech běžet.

Technická specifika (Java): Příkazy a dotazy jako oddělené handlery (např. `CommandHandler` / `QueryHandler`); čtecí model v samostatné tabulce nebo indexu plněný listenerem událostí. Nad Spring Eventing/Kafka listenerem drž offset.

Postup si zvol sám. Pasti v tomto tématu nejsou vypsané: najdi je (souběh, opakované doručení, pořadí, selhání uprostřed) a ke každé napiš test, který by ji odhalil. Na konci vypiš, jaké pasti jsi našel a které jsi vědomě nechal otevřené.

## Co nesmíš měnit

- Stávající veřejné API a chování endpointů kalkulačky, které teď funguje.
- Databázové schéma stávajících tabulek (nové tabulky/migrace jsou v pořádku, přejmenování a mazání ne).
- Cizí testy, seed data a existující hodnoty. Když je změna nutná, dej ji do samostatného commitu a uveď ji. Pokud změna tvaru události nebo nový konzument nutně mění existující test, uprav ho v samostatném commitu a v závěru uveď proč.
- Veřejné kontrakty (API, schémata událostí) bez zpětné kompatibility.

## Hotovo, když

- Příkazy nevracejí čtecí model, dotazy nic nemění; stávající endpointy vracejí stejné odpovědi jako dřív.
- Čtecí model lze smazat a přestavět z událostí nebo ze zápisové tabulky.
- Existuje test pro zastaralé čtení, pro read-your-writes a pro výpadek projekce.
- Zpoždění projekce je měřitelné.
- Dokumentace uvádí, že CQRS nevyžaduje Event Sourcing, a volba zápisové strany je zdůvodněná.
- JUnit 5 testy ve stávající struktuře `src/test/java` projdou build nástrojem projektu.
- V závěru uveď: co jsi změnil, co jsi předpokládal, co zbývá.
# Úkol pro coding agenta: CQRS: příkazy a dotazy

Stack: C#. Režim: s vedením.
Cíl: Oddělit zápis a čtení kalkulačky: příkazy mění zápisový model, dotazy čtou samostatný čtecí model, a změřit zpoždění mezi nimi.

## Kontext existujícího kódu

Pracuješ v existujícím projektu (C# backend (.NET, ASP.NET Core, EF Core nebo Dapper). Použij solution, styl a DI konvence, které projekt už má (xUnit/NUnit).).
Doména: hypoteční kalkulačka, která se postupně mění v event-driven systém (kalkulačka, rizika, schvalování, identita, účet, dokumenty, výplata).
Předpoklad: projekt má event store výpočtů (Event Sourcing) a projekci s checkpointem, kterou jde zastavit a spustit. Pokud ne, nejdřív založ minimální: události s globální pozicí a odběr, který z nich staví čtecí model a ukládá pozici.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Endpointy kalkulačky, které míchají změnu stavu a čtení v jedné službě nebo jednom modelu (DTO, entita, repozitář).
- Dotazy, které dnes skládají pohled z více míst, přehrávají historii nebo dělají drahé dotazy do zápisové tabulky.
- Existující mechanismus publikace změn (události, outbox, broker), který lze použít jako spojnici mezi stranami.
- Místa, kde UI čte data hned po uložení (read-your-writes).
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (čtecí model, čekání na verzi, metriky), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

## Očekávané chování

Příkaz mění stav a hlídá pravidla, dotaz jen čte; každá strana má vlastní model a spojnicí je událost. Čtecí model je chvíli pozadu (eventual consistency): zpoždění jde měřit a dotaz s verzí z příkazu na projekci počká (read-your-writes). CQRS nevyžaduje Event Sourcing. Příklad: výpočet s 250 změnami; bez CQRS každý dotaz přehraje 250 událostí, s CQRS přečte jeden řádek čtecího modelu.

## Konkrétní úkol

Technická specifika (C#): Příkazy a dotazy jako oddělené handlery (MediatR `IRequest` pro Command a Query zvlášť, pokud ho projekt už používá); čtecí model plněný `BackgroundService` z událostí, čtení přes Dapper/EF `AsNoTracking`.

Postupuj po krocích. Po bodu 1 (plán) napiš, co zjistíš a co navrhuješ, a počkej na potvrzení:
1. Plán: vypiš příkazy (mění stav, vrací jen potvrzení a verzi) a dotazy (nic nemění). Nakresli tok Command → Write model → Event → Projection → Query.
2. Zaveď oddělené modely: zápisový model drží pravidla (invarianty), čtecí model má tvar přesně podle dotazu. Příkazy nevracejí čtecí model. Stávající endpointy, které po zápisu vracejí stav, nech; příkazy a dotazy přidej jako nové (např. `/api/v2/commands/…`, `/api/v2/queries/…`).
3. Změnu zveřejni jako událost (v jedné transakci se zápisem) a z ní staví projekce čtecí model. S event storem je událost sám zápis; variantu s obyčejnou tabulkou (řádek + outbox v jedné transakci) jen popiš v dokumentaci.
4. Příkaz vrací verzi. Dotaz přijme `minVersion` a počká nejvýš 1 s (konfigurovatelně), pak vrátí poslední stav se `stale: true`. Bez `minVersion` vrátí hned, co čtecí model má.
5. Změř zpoždění projekce: počet událostí pozadu (poslední pozice v event store − checkpoint) a stáří nejstarší nezpracované události. Vystav je jako metriku, např. `GET /metrics` ve formátu Prometheus. Když event store neumí vrátit poslední pozici, doplň to.
6. Test: po příkazu dotaz s tokenem vrátí novou verzi; bez tokenu smí vrátit starší; při zastavené projekci (`stop()` odběru) zápis funguje dál a po `start()` se čtecí model dožene.

Na co si dát pozor:
- Nezaměňuj CQRS s Event Sourcingem: oddělení modelů nevyžaduje události jako úložiště.
- Nedělej CQRS všude: pro jednoduché CRUD obrazovky přidá jen složitost. V kalkulačce se vyplatí pro detail výpočtu ze streamu a pro souhrny; CRUD formulář nech.
- UI po uložení nesmí předpokládat, že čtecí model je hned aktuální (token verze, optimistické zobrazení).
- Příkaz nesmí číst z čtecího modelu, aby rozhodl o zápisu: rozhodnutí patří zápisové straně.

## Co nesmíš měnit

- Stávající veřejné API a chování endpointů kalkulačky, které teď funguje.
- Databázové schéma stávajících tabulek (nové tabulky/migrace jsou v pořádku, přejmenování a mazání ne).
- Cizí testy, seed data a existující hodnoty. Když je změna nutná, dej ji do samostatného commitu a uveď ji. Pokud změna tvaru události nebo nový konzument nutně mění existující test, uprav ho v samostatném commitu a v závěru uveď proč.
- Veřejné kontrakty (API, schémata událostí) bez zpětné kompatibility.

## Hotovo, když

- Příkazy nevracejí čtecí model, dotazy nic nemění; stávající endpointy vracejí stejné odpovědi jako dřív.
- Čtecí model lze smazat a přestavět z událostí nebo ze zápisové tabulky.
- Existuje test pro zastaralé čtení, pro read-your-writes a pro výpadek projekce.
- Zpoždění projekce je měřitelné.
- Dokumentace uvádí, že CQRS nevyžaduje Event Sourcing, a volba zápisové strany je zdůvodněná.
- Testy ve stávajícím testovacím projektu projdou (`dotnet test`).
- V závěru uveď: co jsi změnil, co jsi předpokládal, co zbývá.
# Úkol pro coding agenta: CQRS: příkazy a dotazy

Stack: C#. Režim: challenge.
Cíl: Oddělit zápis a čtení kalkulačky: příkazy mění zápisový model, dotazy čtou samostatný čtecí model, a změřit zpoždění mezi nimi.

## Kontext existujícího kódu

Pracuješ v existujícím projektu (C# backend (.NET, ASP.NET Core, EF Core nebo Dapper). Použij solution, styl a DI konvence, které projekt už má (xUnit/NUnit).).
Doména: hypoteční kalkulačka, která se postupně mění v event-driven systém (kalkulačka, rizika, schvalování, identita, účet, dokumenty, výplata).
Předpoklad: projekt má event store výpočtů (Event Sourcing) a projekci s checkpointem, kterou jde zastavit a spustit. Pokud ne, nejdřív založ minimální: události s globální pozicí a odběr, který z nich staví čtecí model a ukládá pozici.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Endpointy kalkulačky, které míchají změnu stavu a čtení v jedné službě nebo jednom modelu (DTO, entita, repozitář).
- Dotazy, které dnes skládají pohled z více míst, přehrávají historii nebo dělají drahé dotazy do zápisové tabulky.
- Existující mechanismus publikace změn (události, outbox, broker), který lze použít jako spojnici mezi stranami.
- Místa, kde UI čte data hned po uložení (read-your-writes).
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (čtecí model, čekání na verzi, metriky), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

## Očekávané chování

Příkaz mění stav a hlídá pravidla, dotaz jen čte; každá strana má vlastní model a spojnicí je událost. Čtecí model je chvíli pozadu (eventual consistency): zpoždění jde měřit a dotaz s verzí z příkazu na projekci počká (read-your-writes). CQRS nevyžaduje Event Sourcing. Příklad: výpočet s 250 změnami; bez CQRS každý dotaz přehraje 250 událostí, s CQRS přečte jeden řádek čtecího modelu.

## Zadání (Challenge)

Přidej vedle stávajícího endpointu oddělený čtecí model kalkulačky stavěný z událostí, s tokenem verze pro read-your-writes a metrikou zpoždění projekce. Stávající endpoint nech běžet.

Technická specifika (C#): Příkazy a dotazy jako oddělené handlery (MediatR `IRequest` pro Command a Query zvlášť, pokud ho projekt už používá); čtecí model plněný `BackgroundService` z událostí, čtení přes Dapper/EF `AsNoTracking`.

Postup si zvol sám. Pasti v tomto tématu nejsou vypsané: najdi je (souběh, opakované doručení, pořadí, selhání uprostřed) a ke každé napiš test, který by ji odhalil. Na konci vypiš, jaké pasti jsi našel a které jsi vědomě nechal otevřené.

## Co nesmíš měnit

- Stávající veřejné API a chování endpointů kalkulačky, které teď funguje.
- Databázové schéma stávajících tabulek (nové tabulky/migrace jsou v pořádku, přejmenování a mazání ne).
- Cizí testy, seed data a existující hodnoty. Když je změna nutná, dej ji do samostatného commitu a uveď ji. Pokud změna tvaru události nebo nový konzument nutně mění existující test, uprav ho v samostatném commitu a v závěru uveď proč.
- Veřejné kontrakty (API, schémata událostí) bez zpětné kompatibility.

## Hotovo, když

- Příkazy nevracejí čtecí model, dotazy nic nemění; stávající endpointy vracejí stejné odpovědi jako dřív.
- Čtecí model lze smazat a přestavět z událostí nebo ze zápisové tabulky.
- Existuje test pro zastaralé čtení, pro read-your-writes a pro výpadek projekce.
- Zpoždění projekce je měřitelné.
- Dokumentace uvádí, že CQRS nevyžaduje Event Sourcing, a volba zápisové strany je zdůvodněná.
- Testy ve stávajícím testovacím projektu projdou (`dotnet test`).
- V závěru uveď: co jsi změnil, co jsi předpokládal, co zbývá.
# Úkol pro coding agenta: CQRS: příkazy a dotazy

Stack: Web (HTML/JS nebo stack aplikace). Režim: s vedením.
Cíl: Oddělit zápis a čtení kalkulačky: příkazy mění zápisový model, dotazy čtou samostatný čtecí model, a změřit zpoždění mezi nimi.

## Kontext existujícího kódu

Pracuješ v existujícím projektu (webová aplikace: frontend v prohlížeči, případně s vlastním backendem. Když má projekt backend, patří event store, broker a služby na backend a body o endpointech a tabulkách se týkají jeho. Když backend nemá, použij IndexedDB v prohlížeči a v testech `fake-indexeddb`. Framework a test runner projektu zachovej; nové závislosti přidávej jen s odůvodněním.).
Doména: hypoteční kalkulačka, která se postupně mění v event-driven systém (kalkulačka, rizika, schvalování, identita, účet, dokumenty, výplata).
Předpoklad: projekt má event store výpočtů (Event Sourcing) a projekci s checkpointem, kterou jde zastavit a spustit. Pokud ne, nejdřív založ minimální: události s globální pozicí a odběr, který z nich staví čtecí model a ukládá pozici.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Endpointy kalkulačky, které míchají změnu stavu a čtení v jedné službě nebo jednom modelu (DTO, entita, repozitář).
- Dotazy, které dnes skládají pohled z více míst, přehrávají historii nebo dělají drahé dotazy do zápisové tabulky.
- Existující mechanismus publikace změn (události, outbox, broker), který lze použít jako spojnici mezi stranami.
- Místa, kde UI čte data hned po uložení (read-your-writes).
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (čtecí model, čekání na verzi, metriky), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

## Očekávané chování

Příkaz mění stav a hlídá pravidla, dotaz jen čte; každá strana má vlastní model a spojnicí je událost. Čtecí model je chvíli pozadu (eventual consistency): zpoždění jde měřit a dotaz s verzí z příkazu na projekci počká (read-your-writes). CQRS nevyžaduje Event Sourcing. Příklad: výpočet s 250 změnami; bez CQRS každý dotaz přehraje 250 událostí, s CQRS přečte jeden řádek čtecího modelu.

## Konkrétní úkol

Technická specifika (Web (HTML/JS nebo stack aplikace)): Příkaz vrací `{ id, version }` (verze streamu; u dotazů přes víc výpočtů globální pozice). Oddělené API klienty `commands` a `queries`; po příkazu ulož vrácenou verzi a dotaz opakuj, dokud `version >= minVersion` (nejvýš 3–5 pokusů); zobraz „aktualizuje se…“. Formulář, který volá endpoint mimo event store, nepřepínej: read-your-writes ukaž v samostatné části UI nebo v testu klienta.

Postupuj po krocích. Po bodu 1 (plán) napiš, co zjistíš a co navrhuješ, a počkej na potvrzení:
1. Plán: vypiš příkazy (mění stav, vrací jen potvrzení a verzi) a dotazy (nic nemění). Nakresli tok Command → Write model → Event → Projection → Query.
2. Zaveď oddělené modely: zápisový model drží pravidla (invarianty), čtecí model má tvar přesně podle dotazu. Příkazy nevracejí čtecí model. Stávající endpointy, které po zápisu vracejí stav, nech; příkazy a dotazy přidej jako nové (např. `/api/v2/commands/…`, `/api/v2/queries/…`).
3. Změnu zveřejni jako událost (v jedné transakci se zápisem) a z ní staví projekce čtecí model. S event storem je událost sám zápis; variantu s obyčejnou tabulkou (řádek + outbox v jedné transakci) jen popiš v dokumentaci.
4. Příkaz vrací verzi. Dotaz přijme `minVersion` a počká nejvýš 1 s (konfigurovatelně), pak vrátí poslední stav se `stale: true`. Bez `minVersion` vrátí hned, co čtecí model má.
5. Změř zpoždění projekce: počet událostí pozadu (poslední pozice v event store − checkpoint) a stáří nejstarší nezpracované události. Vystav je jako metriku, např. `GET /metrics` ve formátu Prometheus. Když event store neumí vrátit poslední pozici, doplň to.
6. Test: po příkazu dotaz s tokenem vrátí novou verzi; bez tokenu smí vrátit starší; při zastavené projekci (`stop()` odběru) zápis funguje dál a po `start()` se čtecí model dožene.

Na co si dát pozor:
- Nezaměňuj CQRS s Event Sourcingem: oddělení modelů nevyžaduje události jako úložiště.
- Nedělej CQRS všude: pro jednoduché CRUD obrazovky přidá jen složitost. V kalkulačce se vyplatí pro detail výpočtu ze streamu a pro souhrny; CRUD formulář nech.
- UI po uložení nesmí předpokládat, že čtecí model je hned aktuální (token verze, optimistické zobrazení).
- Příkaz nesmí číst z čtecího modelu, aby rozhodl o zápisu: rozhodnutí patří zápisové straně.

## Co nesmíš měnit

- Stávající veřejné API a chování endpointů kalkulačky, které teď funguje.
- Databázové schéma stávajících tabulek (nové tabulky/migrace jsou v pořádku, přejmenování a mazání ne).
- Cizí testy, seed data a existující hodnoty. Když je změna nutná, dej ji do samostatného commitu a uveď ji. Pokud změna tvaru události nebo nový konzument nutně mění existující test, uprav ho v samostatném commitu a v závěru uveď proč.
- Veřejné kontrakty (API, schémata událostí) bez zpětné kompatibility.

## Hotovo, když

- Příkazy nevracejí čtecí model, dotazy nic nemění; stávající endpointy vracejí stejné odpovědi jako dřív.
- Čtecí model lze smazat a přestavět z událostí nebo ze zápisové tabulky.
- Existuje test pro zastaralé čtení, pro read-your-writes a pro výpadek projekce.
- Zpoždění projekce je měřitelné.
- Dokumentace uvádí, že CQRS nevyžaduje Event Sourcing, a volba zápisové strany je zdůvodněná.
- Testy projdou stávajícím test runnerem; lint a build projdou, pokud je projekt má.
- V závěru uveď: co jsi změnil, co jsi předpokládal, co zbývá.
# Úkol pro coding agenta: CQRS: příkazy a dotazy

Stack: Web (HTML/JS nebo stack aplikace). Režim: challenge.
Cíl: Oddělit zápis a čtení kalkulačky: příkazy mění zápisový model, dotazy čtou samostatný čtecí model, a změřit zpoždění mezi nimi.

## Kontext existujícího kódu

Pracuješ v existujícím projektu (webová aplikace: frontend v prohlížeči, případně s vlastním backendem. Když má projekt backend, patří event store, broker a služby na backend a body o endpointech a tabulkách se týkají jeho. Když backend nemá, použij IndexedDB v prohlížeči a v testech `fake-indexeddb`. Framework a test runner projektu zachovej; nové závislosti přidávej jen s odůvodněním.).
Doména: hypoteční kalkulačka, která se postupně mění v event-driven systém (kalkulačka, rizika, schvalování, identita, účet, dokumenty, výplata).
Předpoklad: projekt má event store výpočtů (Event Sourcing) a projekci s checkpointem, kterou jde zastavit a spustit. Pokud ne, nejdřív založ minimální: události s globální pozicí a odběr, který z nich staví čtecí model a ukládá pozici.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Endpointy kalkulačky, které míchají změnu stavu a čtení v jedné službě nebo jednom modelu (DTO, entita, repozitář).
- Dotazy, které dnes skládají pohled z více míst, přehrávají historii nebo dělají drahé dotazy do zápisové tabulky.
- Existující mechanismus publikace změn (události, outbox, broker), který lze použít jako spojnici mezi stranami.
- Místa, kde UI čte data hned po uložení (read-your-writes).
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (čtecí model, čekání na verzi, metriky), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

## Očekávané chování

Příkaz mění stav a hlídá pravidla, dotaz jen čte; každá strana má vlastní model a spojnicí je událost. Čtecí model je chvíli pozadu (eventual consistency): zpoždění jde měřit a dotaz s verzí z příkazu na projekci počká (read-your-writes). CQRS nevyžaduje Event Sourcing. Příklad: výpočet s 250 změnami; bez CQRS každý dotaz přehraje 250 událostí, s CQRS přečte jeden řádek čtecího modelu.

## Zadání (Challenge)

Přidej vedle stávajícího endpointu oddělený čtecí model kalkulačky stavěný z událostí, s tokenem verze pro read-your-writes a metrikou zpoždění projekce. Stávající endpoint nech běžet.

Technická specifika (Web (HTML/JS nebo stack aplikace)): Příkaz vrací `{ id, version }` (verze streamu; u dotazů přes víc výpočtů globální pozice). Oddělené API klienty `commands` a `queries`; po příkazu ulož vrácenou verzi a dotaz opakuj, dokud `version >= minVersion` (nejvýš 3–5 pokusů); zobraz „aktualizuje se…“. Formulář, který volá endpoint mimo event store, nepřepínej: read-your-writes ukaž v samostatné části UI nebo v testu klienta.

Postup si zvol sám. Pasti v tomto tématu nejsou vypsané: najdi je (souběh, opakované doručení, pořadí, selhání uprostřed) a ke každé napiš test, který by ji odhalil. Na konci vypiš, jaké pasti jsi našel a které jsi vědomě nechal otevřené.

## Co nesmíš měnit

- Stávající veřejné API a chování endpointů kalkulačky, které teď funguje.
- Databázové schéma stávajících tabulek (nové tabulky/migrace jsou v pořádku, přejmenování a mazání ne).
- Cizí testy, seed data a existující hodnoty. Když je změna nutná, dej ji do samostatného commitu a uveď ji. Pokud změna tvaru události nebo nový konzument nutně mění existující test, uprav ho v samostatném commitu a v závěru uveď proč.
- Veřejné kontrakty (API, schémata událostí) bez zpětné kompatibility.

## Hotovo, když

- Příkazy nevracejí čtecí model, dotazy nic nemění; stávající endpointy vracejí stejné odpovědi jako dřív.
- Čtecí model lze smazat a přestavět z událostí nebo ze zápisové tabulky.
- Existuje test pro zastaralé čtení, pro read-your-writes a pro výpadek projekce.
- Zpoždění projekce je měřitelné.
- Dokumentace uvádí, že CQRS nevyžaduje Event Sourcing, a volba zápisové strany je zdůvodněná.
- Testy projdou stávajícím test runnerem; lint a build projdou, pokud je projekt má.
- V závěru uveď: co jsi změnil, co jsi předpokládal, co zbývá.

Školení pro váš tým

Chcete školení pro svůj tým?

Školení vedeme na vašem kódu a vašem doménovém problému. Domluvíme rozsah, termín a formu.

Probrat školení