DÍL 07a KAPITOLA 07 · SEKCE 07.01–07.07

Aggregate design: hranice, invarianty, transakce

Kde vést hranici agregátu: invarianty jako východisko, malé agregáty podle Vernonových vodítek, jeden agregát na transakci, eventual consistency přes ságu a odkazy mezi agregáty jen přes ID.

Přehrávač YouTube se načte až po kliknutí · otevřít na YouTube (nové okno) ↗

Přepis dílu komentář tak, jak zazní ve videu

Dva úkoly, jedna verze

Modelový příklad. Dva lidé v týmu upraví ve stejnou chvíli dva úkoly v témže projektu. Úkoly spolu vůbec nesouvisejí. Přesto jeden z nich dostane OptimisticLockException.

Optimistický zámek v Doctrine hlídá verzi kořene agregátu a kdo ukládá se starou verzí, dostane výjimku. Jenže agregátem je tu celý projekt i s tisícem úkolů. Obě změny tak míří na jednu a tutéž verzi.

Invarianty jako východisko návrhu

Agregát odpovídá na dvě otázky, které objektový model sám neřeší. Kdo hlídá invarianty? A co se uloží v jedné transakci?

Bez jasné hranice se model kazí dvěma směry. Buď přeroste do jedné obří transakce se zámky a deadlocky, nebo se rozpadne na tenké objekty a logika skončí ve službách.

Kde tedy hranici vést? Ze schématu databáze ani z ER diagramu ji neodvodíte. Vychází z invariantů.

Kniha jmenuje pět typických zdrojů. Sumační pravidla, stavové přechody, existenční pravidla, kvantitativní limity a vzájemné závislosti polí.

U každého pravidla pak rozhoduje jedna otázka. Musí být porušení nemožné v každý okamžik? Nebo stačí, když se stav dorovná se zpožděním?

Zkuste si to. Tři pravidla z kapitoly. Součet položek faktury se rovná celkové ceně. Uživatel s podepsanou smlouvou dostane vítací e-mail. A bilance debetů a kreditů je nulová.

Součet a bilance musí platit pořád, takže patří do jednoho agregátu. Vítací e-mail klidně odejde o pár sekund později. Postará se o něj sága, tedy proces, který další kroky provede ve vlastních transakcích.

Pomůže i otázka, kterou Vernon převzal od Evanse: čí je to práce, udržet ta data konzistentní? Když je to práce uživatele, který akci spouští, patří pravidlo do jedné transakce. Jinak stačí dorovnat.

Kandidáty máte už ze stěny Event Stormingu. Jsou to růžové hot spoty a lila papírky s pravidly, tedy policy.

Každý z nich pak přepíšete do věty: v každý okamžik musí platit. Když se věta neobejde bez slova časem, je to kandidát na ságu.

U objednávky to dopadne takhle. Order a jeho položky sdílejí pravidla, třeba že potvrzená objednávka má aspoň jednu položku. Zákazník je samostatný agregát a objednávka si od něj drží jen identifikátor.

Malé agregáty: Vernonova vodítka

Vaughn Vernon shrnul pasti návrhu agregátu do tří esejí Effective Aggregate Design z roku 2011. Sám je označuje za rules of thumb, tedy vodítka, a rozebírá i to, kdy se vědomě porušují.

Ve třetím díle je shrne do čtyř bodů. Skutečné invarianty uvnitř konzistenční hranice. Malé agregáty. Odkazy na jiné agregáty jen přes identitu. A mimo hranici eventual consistency, když padne otázka, čí je to práce.

Jak malé? Výchozí volba je kořen a pár hodnotových objektů. Vernon cituje zkušenost Niclase Hedhmana: na jeho projektu tvořil zhruba sedmdesát procent agregátů jen kořen s hodnotovými objekty. Zbytek měl dvě až tři entity.

Za univerzální poměr to ale Vernon výslovně nepovažuje.

Pátou zásadu Vernon nečísluje: jedna transakce mění jednu instanci agregátu. Khononov z ní v knize Learning Domain-Driven Design dělá samostatné pravidlo.

Krátký odhad. Command handler volá save na dva různé repozitáře. Co to naznačuje?

Hranici je potřeba znovu prověřit. Buď z toho mají být dva commandy, nebo jde o ságu.

Zpátky k projektu z úvodu. Velký agregát vypadá bezpečně, ale kniha u něj počítá tři ceny.

První je souběh. Kniha doporučuje, aby doménová metoda při změně vnitřní entity zvedla i verzi kořene. Pak větší agregát znamená víc konfliktů a odtud je ta výjimka z úvodu.

Druhá je paměť: načte se tisíc úkolů, i když se mění jediný. A třetí jsou pravidla, která spolu věcně nesouvisejí, a přesto se kontrolují naráz.

Objednávku s desítkami položek obvykle upravuje jediný zákazník, a to je v pořádku. Projekt s tisícem úkolů ale sdílí celý tým a konflikt hrozí každému s každým.

Agregátu, do kterého postupně přibylo všechno, co s kořenem nějak souvisí, říká kniha God Aggregate. Náprava je otázka na každý sloupec uvnitř. Mění se jen spolu s kořenem, nebo i samostatně?

Úkol má vlastní životní cyklus. Stane se tedy samostatným agregátem a na projekt odkazuje přes ProjectId.

Jeden agregát na transakci

Jeden agregát na transakci patří k nejpřísnějším vodítkům. V Symfony projektech se přitom porušuje často. Učebnicový příklad je převod peněz mezi dvěma účty.

Handler otevře transakci a načte oba účty. Z jednoho peníze strhne, na druhý je připíše a commit proběhne atomicky. Vypadá to bezpečně.

Jenže dva agregáty, které se mění v jedné transakci, jsou prakticky jeden agregát ve dvou třídách. Optimistický zámek hlídá jedinou instanci. Kdo chce pokrýt oba účty, skončí u pesimistického zámku, a ten zvyšuje riziko deadlocku.

Stačí dva souběžné převody opačným směrem, jeden z účtu A na B a druhý z B na A. Každý pak čeká na zámek, který drží ten druhý.

Správná verze sahá jen na jeden agregát. Handler načte zdrojový účet a zavolá withdraw. Kromě částky předá i cílový účet a identifikátor převodu.

Metoda withdraw zaznamená událost MoneyWithdrawn. Na ni zareaguje sága a v samostatné transakci připíše peníze na cílový účet. Když to selže, provede kompenzaci.

Souběžné výběry pak hlídá optimistický zámek zdrojového účtu.

Vernon ale připouští čtyři situace, kdy tým vodítko vědomě poruší. Dávka z formuláře. Chybějící messaging. Globální transakce, které předepíše firma. A výkon dotazů.

Rozhoduje, kolik lidí sahá na tytéž instance současně. Vernon tomu říká user-aggregate affinity. Když na nich pracuje jediný člověk, je porušení levné.

Zkuste odhadnout. Formulář zakládá deset položek naráz, všechny v jedné transakci. Dá se to obhájit?

Dá, pokud je to sémanticky totéž jako deset položek založených po jedné. Hranici ale prověřte dřív, než to prohlásíte za výjimku.

Eventual consistency a reference přes ID

Místo transakce přes víc agregátů přijdou čtyři kroky. Agregát A zaznamená událost a outbox ji spolehlivě dopraví do brokeru. Handler nebo sága pak v samostatné transakci změní agregát B. Při selhání přijde kompenzace nebo nový pokus.

Jak dlouho smí nekonzistence trvat, rozhoduje doménový expert. Bývá velkorysejší, než se čeká. Podle Vernona experti běžně snesou sekundy, minuty, hodiny, někdy i dny.

Příkladem je checkout v e-shopu. Handler uloží objednávku a publikuje OrderPlaced. Jedna sága pak sníží zásoby, druhá pošle potvrzovací e-mail. Každá běží ve vlastní transakci.

Když je zboží mezitím vyprodané, událost OrderCancelledDueToOutOfStock spustí kompenzaci a objednávka se stornuje.

Třetí vodítko říká, že se mezi agregáty odkazuje přes identitu. Objektová reference totiž svádí k řetězení.

Tenhle řádek mění dva agregáty v jedné transakci a programátor o tom často ani neví.

Lazy loading v Doctrine to jen schová. Co se přes proxy změní, uloží nejbližší flush spolu s objednávkou.

Order proto drží jen CustomerId. Identifikátor funguje stejně v monolitu i v microservices a doménová událost ho přes broker přenese beze ztráty.

Přímou referenci Vernon připouští, když zlevní dotazy. Pak ale slouží jen ke čtení. Kdo přes ni zavolá doménovou metodu cizího agregátu, roztáhl transakci přes hranici.

Jak vypadá invariant uvnitř hranice? Ukázkou je metoda addItem na kanonickém Order a kontrolu stavu z ní znáte z minulého dílu. Co se stane, když zákazník přidá stejný produkt podruhé?

Verze z minulého dílu přidala položku pokaždé. Kanonický Order proto hlídá pravidlo jedna položka na produkt. Když produkt v objednávce už je, jen zvýší množství.

OrderItemAdded se zaznamená v obou větvích. Verzi kořene přitom addItem vědomě nezvedá. Položky se mění jen v rozpracované objednávce jediného zákazníka, takže jde o user-aggregate affinity z minulé části.

Otázky a shrnutí

Otázka na rozmyšlenou. Podle čeho se kreslí hranice agregátu?

Podle invariantů, které musí platit v každý okamžik. Schéma databáze ani asociace v Doctrine ji neurčují.

A jedna z dílu o Event Stormingu. Co se na stěně píše pod velký žlutý papírek agregátu?

Invarianty. A právě podle nich dnes vedla hranice.

Takže v kostce. Hranici agregátu určují invarianty a otázka, čí je to práce.

Jedna transakce mění jeden agregát a zbytek dorovná událost se ságou. Mezi agregáty putují identifikátory a pravidla uvnitř hlídá kořen.

Příští díl vezme agregát do Doctrine a pod zátěž. Celou kapitolu najdete v knize na ddd-v-symfony.katuscak.cz.

← Všechny díly videokurzu