Cvičení 06 · Dva poradci, jedna kalkulace

Optimistic concurrency

  • expectedVersion
  • konflikt
  • invariant

Princip

Dva uživatelé načtou stejnou verzi agregátu. Poradce B zvýší cenu nemovitosti, poradce A (se starým stavem) výpočet dokončí.

Bez kontroly verze se oba zápisy provedou a vznikne výpočet, který porušuje limit splátky.

Optimistic concurrency: zápis nese expectedVersion. Když se stream mezitím změnil, zápis se odmítne a klient načte novou verzi a rozhodne znovu.

Co sledovat

  • Verze, kterou má každý poradce načtenou.
  • Hláška o konfliktu verzí vs. tichý zápis s porušeným invariantem.

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

Bez řešení

Spustit souběh bez kontroly verze

Oba poradci načtou verzi 1. B změní cenu na 9,5 M. A pak dokončí výpočet nad zastaralým stavem.

Spustit v simulátoru bez řešení

S řešením

Spustit s optimistic concurrency

Stejný souběh, ale zápis A se odmítne. Klient A načte novou verzi, uvidí splátku nad limitem a rozhodne se jinak.

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

Co udělat: Klikni na „Spustit souběh bez kontroly verze“. Na co se dívat: Druhý poradce dokončí výpočet nad zastaralou verzí a překročí limit splátky. 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.

Chránit zápis do streamu kontrolou očekávané verze a ošetřit konflikt na API i v UI.

Jazyk
Varianta
# Úkol pro coding agenta: Optimistic concurrency

Stack: Java. Režim: s vedením.
Cíl: Chránit zápis do streamu kontrolou očekávané verze a ošetřit konflikt na API i v UI.

## 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) se zápisem přes `append`. Pokud ne, nejdřív založ minimální: události jen přidávané na konec s verzí streamu.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Místo, kde se zapisují události, kdo volá `append` (i v testech) a jestli existuje unikátní index (streamId, version).
- Jak API vrací chyby (kód pro konflikt) a jestli 409 už neznamená něco jiného.
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (kontrola verze, opakování příkazu), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

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

Dva poradci načtou verzi 1. B změní cenu nemovitosti na 9,5 M (verze 2), A pak nad starým stavem výpočet dokončí; bez kontroly verze projdou oba zápisy a splátka překročí limit. Zápis proto nese očekávanou verzi: dokončení od A s verzí 1 se odmítne s aktuální verzí 2 a A po načtení nové verze rozhodne znovu.

## Konkrétní úkol

Technická specifika (Java): Unikátní index + zachycení `DuplicateKeyException`; test s `ExecutorService` a Testcontainers.

Postupuj po krocích. Po bodu 1 (plán) napiš, co zjistíš a co navrhuješ, a počkej na potvrzení:
1. Plán: kde klient dostane verzi a jak ji pošle zpět (ETag/If-Match nebo pole v těle).
2. `append(streamId, expectedVersion, events)` v jedné transakci porovná poslední verzi s `expectedVersion` (i vyšší, než stream má) a při nesouladu nezapíše nic; unikátní index je pojistka. Spoléhají-li cizí testy na starou signaturu, ponech ji.
3. API vrací 409 Conflict s aktuální verzí a `details.code = "version-conflict"` (u `If-Match` HTTP předepisuje 412; když ho zvolíš, uveď to).
4. Politika: automatický retry jen novým načtením stavu a novým `decide`, nikdy zápisem starých událostí ani u příkazu s verzí od klienta; jinak chyba uživateli.
5. Test souběhu: dva klienti zapíší se stejnou verzí, právě jeden uspěje. Synchronní handler požadavky neprostřídá: použij verzi od klienta nebo dvojníka, který vloží zápis mezi `load` a `append`.

Na co si dát pozor:
- Kontrola „přečti verzi a pak zapiš“ bez atomicity je jen závod ve zpomaleném záběru.
- Automatický retry může porušit invariant.

## 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ž

- Souběžný zápis nad stejnou verzí uspěje jen jednou; retry po konfliktu neporuší invariant.
- API vrací 409 (nebo zvolený 412) a aktuální verzi; UI nabídne načtení nové verze.
- Test běží proti reálné databázi, ne jen mocku (aplikace bez DB: vestavěná, např. `node:sqlite`).
- 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: Optimistic concurrency

Stack: Java. Režim: challenge.
Cíl: Chránit zápis do streamu kontrolou očekávané verze a ošetřit konflikt na API i v UI.

## 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) se zápisem přes `append`. Pokud ne, nejdřív založ minimální: události jen přidávané na konec s verzí streamu.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Místo, kde se zapisují události, kdo volá `append` (i v testech) a jestli existuje unikátní index (streamId, version).
- Jak API vrací chyby (kód pro konflikt) a jestli 409 už neznamená něco jiného.
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (kontrola verze, opakování příkazu), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

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

Dva poradci načtou verzi 1. B změní cenu nemovitosti na 9,5 M (verze 2), A pak nad starým stavem výpočet dokončí; bez kontroly verze projdou oba zápisy a splátka překročí limit. Zápis proto nese očekávanou verzi: dokončení od A s verzí 1 se odmítne s aktuální verzí 2 a A po načtení nové verze rozhodne znovu.

## Zadání (Challenge)

Zaveď optimistic concurrency na zápis do streamu kalkulačky včetně 409 odpovědi a testu dvou souběžných zápisů.

Technická specifika (Java): Unikátní index + zachycení `DuplicateKeyException`; test s `ExecutorService` a Testcontainers.

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ž

- Souběžný zápis nad stejnou verzí uspěje jen jednou; retry po konfliktu neporuší invariant.
- API vrací 409 (nebo zvolený 412) a aktuální verzi; UI nabídne načtení nové verze.
- Test běží proti reálné databázi, ne jen mocku (aplikace bez DB: vestavěná, např. `node:sqlite`).
- 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: Optimistic concurrency

Stack: C#. Režim: s vedením.
Cíl: Chránit zápis do streamu kontrolou očekávané verze a ošetřit konflikt na API i v UI.

## 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) se zápisem přes `append`. Pokud ne, nejdřív založ minimální: události jen přidávané na konec s verzí streamu.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Místo, kde se zapisují události, kdo volá `append` (i v testech) a jestli existuje unikátní index (streamId, version).
- Jak API vrací chyby (kód pro konflikt) a jestli 409 už neznamená něco jiného.
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (kontrola verze, opakování příkazu), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

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

Dva poradci načtou verzi 1. B změní cenu nemovitosti na 9,5 M (verze 2), A pak nad starým stavem výpočet dokončí; bez kontroly verze projdou oba zápisy a splátka překročí limit. Zápis proto nese očekávanou verzi: dokončení od A s verzí 1 se odmítne s aktuální verzí 2 a A po načtení nové verze rozhodne znovu.

## Konkrétní úkol

Technická specifika (C#): Unikátní index + zachycení `DbUpdateException`; test s `Task.WhenAll`.

Postupuj po krocích. Po bodu 1 (plán) napiš, co zjistíš a co navrhuješ, a počkej na potvrzení:
1. Plán: kde klient dostane verzi a jak ji pošle zpět (ETag/If-Match nebo pole v těle).
2. `append(streamId, expectedVersion, events)` v jedné transakci porovná poslední verzi s `expectedVersion` (i vyšší, než stream má) a při nesouladu nezapíše nic; unikátní index je pojistka. Spoléhají-li cizí testy na starou signaturu, ponech ji.
3. API vrací 409 Conflict s aktuální verzí a `details.code = "version-conflict"` (u `If-Match` HTTP předepisuje 412; když ho zvolíš, uveď to).
4. Politika: automatický retry jen novým načtením stavu a novým `decide`, nikdy zápisem starých událostí ani u příkazu s verzí od klienta; jinak chyba uživateli.
5. Test souběhu: dva klienti zapíší se stejnou verzí, právě jeden uspěje. Synchronní handler požadavky neprostřídá: použij verzi od klienta nebo dvojníka, který vloží zápis mezi `load` a `append`.

Na co si dát pozor:
- Kontrola „přečti verzi a pak zapiš“ bez atomicity je jen závod ve zpomaleném záběru.
- Automatický retry může porušit invariant.

## 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ž

- Souběžný zápis nad stejnou verzí uspěje jen jednou; retry po konfliktu neporuší invariant.
- API vrací 409 (nebo zvolený 412) a aktuální verzi; UI nabídne načtení nové verze.
- Test běží proti reálné databázi, ne jen mocku (aplikace bez DB: vestavěná, např. `node:sqlite`).
- 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: Optimistic concurrency

Stack: C#. Režim: challenge.
Cíl: Chránit zápis do streamu kontrolou očekávané verze a ošetřit konflikt na API i v UI.

## 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) se zápisem přes `append`. Pokud ne, nejdřív založ minimální: události jen přidávané na konec s verzí streamu.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Místo, kde se zapisují události, kdo volá `append` (i v testech) a jestli existuje unikátní index (streamId, version).
- Jak API vrací chyby (kód pro konflikt) a jestli 409 už neznamená něco jiného.
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (kontrola verze, opakování příkazu), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

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

Dva poradci načtou verzi 1. B změní cenu nemovitosti na 9,5 M (verze 2), A pak nad starým stavem výpočet dokončí; bez kontroly verze projdou oba zápisy a splátka překročí limit. Zápis proto nese očekávanou verzi: dokončení od A s verzí 1 se odmítne s aktuální verzí 2 a A po načtení nové verze rozhodne znovu.

## Zadání (Challenge)

Zaveď optimistic concurrency na zápis do streamu kalkulačky včetně 409 odpovědi a testu dvou souběžných zápisů.

Technická specifika (C#): Unikátní index + zachycení `DbUpdateException`; test s `Task.WhenAll`.

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ž

- Souběžný zápis nad stejnou verzí uspěje jen jednou; retry po konfliktu neporuší invariant.
- API vrací 409 (nebo zvolený 412) a aktuální verzi; UI nabídne načtení nové verze.
- Test běží proti reálné databázi, ne jen mocku (aplikace bez DB: vestavěná, např. `node:sqlite`).
- 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: Optimistic concurrency

Stack: Web (HTML/JS nebo stack aplikace). Režim: s vedením.
Cíl: Chránit zápis do streamu kontrolou očekávané verze a ošetřit konflikt na API i v UI.

## 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) se zápisem přes `append`. Pokud ne, nejdřív založ minimální: události jen přidávané na konec s verzí streamu.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Místo, kde se zapisují události, kdo volá `append` (i v testech) a jestli existuje unikátní index (streamId, version).
- Jak API vrací chyby (kód pro konflikt) a jestli 409 už neznamená něco jiného.
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (kontrola verze, opakování příkazu), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

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

Dva poradci načtou verzi 1. B změní cenu nemovitosti na 9,5 M (verze 2), A pak nad starým stavem výpočet dokončí; bez kontroly verze projdou oba zápisy a splátka překročí limit. Zápis proto nese očekávanou verzi: dokončení od A s verzí 1 se odmítne s aktuální verzí 2 a A po načtení nové verze rozhodne znovu.

## Konkrétní úkol

Technická specifika (Web (HTML/JS nebo stack aplikace)): Verzi porovnej ve stejné transakci jako zápis, tam, kde je event store (backend, nebo IndexedDB); konflikt vrať jako chybu a nabídni načtení nové verze.

Postupuj po krocích. Po bodu 1 (plán) napiš, co zjistíš a co navrhuješ, a počkej na potvrzení:
1. Plán: kde klient dostane verzi a jak ji pošle zpět (ETag/If-Match nebo pole v těle).
2. `append(streamId, expectedVersion, events)` v jedné transakci porovná poslední verzi s `expectedVersion` (i vyšší, než stream má) a při nesouladu nezapíše nic; unikátní index je pojistka. Spoléhají-li cizí testy na starou signaturu, ponech ji.
3. API vrací 409 Conflict s aktuální verzí a `details.code = "version-conflict"` (u `If-Match` HTTP předepisuje 412; když ho zvolíš, uveď to).
4. Politika: automatický retry jen novým načtením stavu a novým `decide`, nikdy zápisem starých událostí ani u příkazu s verzí od klienta; jinak chyba uživateli.
5. Test souběhu: dva klienti zapíší se stejnou verzí, právě jeden uspěje. Synchronní handler požadavky neprostřídá: použij verzi od klienta nebo dvojníka, který vloží zápis mezi `load` a `append`.

Na co si dát pozor:
- Kontrola „přečti verzi a pak zapiš“ bez atomicity je jen závod ve zpomaleném záběru.
- Automatický retry může porušit invariant.

## 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ž

- Souběžný zápis nad stejnou verzí uspěje jen jednou; retry po konfliktu neporuší invariant.
- API vrací 409 (nebo zvolený 412) a aktuální verzi; UI nabídne načtení nové verze.
- Test běží proti reálné databázi, ne jen mocku (aplikace bez DB: vestavěná, např. `node:sqlite`).
- 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: Optimistic concurrency

Stack: Web (HTML/JS nebo stack aplikace). Režim: challenge.
Cíl: Chránit zápis do streamu kontrolou očekávané verze a ošetřit konflikt na API i v UI.

## 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) se zápisem přes `append`. Pokud ne, nejdřív založ minimální: události jen přidávané na konec s verzí streamu.

Nejdřív čti, nic neměň. Najdi a stručně shrň:
- Místo, kde se zapisují události, kdo volá `append` (i v testech) a jestli existuje unikátní index (streamId, version).
- Jak API vrací chyby (kód pro konflikt) a jestli 409 už neznamená něco jiného.
- Které z těchto částí už v projektu existují a které chybí. Než napíšeš vlastní infrastrukturu (kontrola verze, opakování příkazu), ověř, jestli ji projekt nebo organizace nemá jako hotovou knihovnu, a použij ji.

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

Dva poradci načtou verzi 1. B změní cenu nemovitosti na 9,5 M (verze 2), A pak nad starým stavem výpočet dokončí; bez kontroly verze projdou oba zápisy a splátka překročí limit. Zápis proto nese očekávanou verzi: dokončení od A s verzí 1 se odmítne s aktuální verzí 2 a A po načtení nové verze rozhodne znovu.

## Zadání (Challenge)

Zaveď optimistic concurrency na zápis do streamu kalkulačky včetně 409 odpovědi a testu dvou souběžných zápisů.

Technická specifika (Web (HTML/JS nebo stack aplikace)): Verzi porovnej ve stejné transakci jako zápis, tam, kde je event store (backend, nebo IndexedDB); konflikt vrať jako chybu a nabídni načtení nové verze.

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ž

- Souběžný zápis nad stejnou verzí uspěje jen jednou; retry po konfliktu neporuší invariant.
- API vrací 409 (nebo zvolený 412) a aktuální verzi; UI nabídne načtení nové verze.
- Test běží proti reálné databázi, ne jen mocku (aplikace bez DB: vestavěná, např. `node:sqlite`).
- 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í