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ímCo 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.
# Ú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á.Související části prezentace
Š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í