Event Sourcing v DDD a Symfony
Event Sourcing v kontextu Domain-Driven Design a Symfony – implementace Event Store, event-sourcovaných agregátů, projekcí, Outbox patternu, snapshottingu a verzování událostí. Včetně praktických problémů: idempotence projektorů, rebuild projekcí a eventual consistency.
Obsah kapitoly
13.01 Co je Event Sourcing?#
Tradiční CRUD persistence má slepou skvrnu: při každé změně přepíše předchozí stav a veškerá historie se nenávratně ztrácí. Event Sourcing (ES) ukládá stav systému jako sekvenci neměnných událostí, jež k němu vedly [1]. Každá změna stavu domény je zaznamenána jako samostatná, pojmenovaná událost se svými daty. Aktuální stav agregátu pak vzniká přehráním (replay) těchto událostí od počátku.
Princip lze vyjádřit větou: current state is derived from the history of events. Namísto jediného řádku v databázové tabulce, který je při každé změně přepisován, existuje append-only log všech událostí, jež kdy na agregátu nastaly.
Porovnání s tradiční CRUD persistencí
V klasickém CRUD přístupu drží tabulka pouze aktuální stav entity – jakmile se hodnota změní, předchozí je pryč. Event Sourcing zapisuje každou změnu jako nový řádek event logu, takže žádná informace se nikdy nepřepisuje ani nemaže.
13.02 Vztah k CQRS#
Event Sourcing a CQRS jsou dva samostatné vzory [2]. Nejsou totéž – lze aplikovat CQRS bez Event Sourcingu a naopak ES bez CQRS. V praxi DDD aplikací se ale obvykle objevují společně.
Důvod je technický: Event Sourcing produkuje události jako základní artefakt persistence a CQRS potřebuje způsob, jak aktualizovat read modely při každé změně write strany. Události tuto propagaci pokrývají bez další infrastruktury – write side uloží událost do Event Store, read side ji přečte a aktualizuje projekci.
13.03 Kdy použít Event Sourcing#
Event Sourcing přidává konkrétní možnosti – auditní log, replay, temporální dotazy – výměnou za vyšší složitost infrastruktury i kódu. Před zavedením stojí za úvahu, zda v daném kontextu přínosy převažují nad náklady na implementaci a provoz. Tato sekce dává rozhodovací rámec; zbytek kapitoly rozebírá implementaci.
Vhodné případy užití
- Auditní log jako doménový požadavek – Finanční systémy, zdravotnické záznamy nebo jakákoli doména, kde je zákonná povinnost uchovávat kompletní historii změn. Auditní log v ES vychází přímo z formátu úložiště – nepotřebuje samostatnou implementaci.
- Komplexní doménová logika s bohatými stavovými přechody – Agregáty procházejí mnoha stavy, každý přechod má svou sémantiku a musí být rekonstruovatelný. Typicky: objednávkové systémy, workflow enginy, bankovní transakce.
- Temporální dotazy – Potřeba „přehrát“ stav systému k libovolnému bodu v minulosti (debugging, analýza, „what-if“ scénáře). U ES stačí replay eventů do daného timestampu.
- Event-driven integrace – Systém produkuje události, které konzumují jiné bounded contexts nebo externí systémy. ES zajišťuje, že žádná událost nebude ztracena – Event Store je zdrojem pravdy pro integraci.
- CQRS s vysokou čtecí zátěží – ES umožňuje vybudovat libovolný počet optimalizovaných read modelů z jednoho event streamu, aniž by bylo nutné měnit write model.
Nevhodné případy užití
- Jednoduché CRUD aplikace – Pokud doménová logika spočívá v základních operacích Create/Read/Update/Delete bez složitých stavových přechodů, ES přináší jen zbytečnou složitost.
- Systémy orientované převážně na reporting – Pokud je primárním požadavkem rychlé čtení a agregace dat (BI, analytics), jsou vhodnější klasická DW řešení nebo OLAP databáze.
- Prototypy a MVP – Rychlá validace produktového nápadu nepotřebuje složitou infrastrukturu. ES lze přidat do zralého systému inkrementálně, pokud se ukáže potřeba – viz Migrace z CRUD.
- Týmy bez zkušeností s ES – Implementace Event Sourcingu bez předchozí zkušenosti přináší vysoké riziko chyb v kritické infrastruktuře (Event Store, serializace, versioning). Začíná se typicky menším bounded contextem jako experimentem.
Hotové knihovny, nebo vlastní store?
Padne-li rozhodnutí pro Event Sourcing, zbývá volba implementace. PHP ekosystém nabízí několik knihoven s odlišnou váhou i filozofií:
- EventSauce – doporučený výchozí bod. Malé jádro, srozumitelná dokumentace a žádná vazba na konkrétní framework; integraci do Symfony si napíšete sami v řádu desítek řádků.
- Broadway – starší knihovna s historicky silnou vazbou na Symfony. Funguje, ale tempo vývoje za poslední roky zvolnilo.
- prooph – dlouho platil za standard PHP Event Sourcingu, dnes jej udržuje komunita. Pro nové projekty bývá první volbou spíš EventSauce.
- Ecotone – ne knihovna, ale celý framework s ES, CQRS a Sagami. Má výraznou filozofii postavenou na message-driven architektuře; přijímáte ji vcelku, ne po částech.
Vlastní minimalistický store má smysl ve třech situacích: při učení, kdy chcete vidět principy bez vrstvy cizích abstrakcí, při požadavku na plnou kontrolu nad schématem a serializací, a u malé domény s několika typy událostí, kde by knihovna byla větší než problém. Tato kniha staví vlastní store z prvního důvodu – ukázky slouží k výuce principů, nikoli jako náhrada prověřené knihovny v produkci.
13.04 Doménové události jako základ Event Sourcingu#
V Event Sourcingu jsou doménové události (Domain Events) zdrojem pravdy o stavu systému – nejen notifikací o vedlejších efektech, jako je tomu u událostí v Doctrine ORM aplikaci. Tomu odpovídají i přísnější požadavky na jejich tvar:
První dva požadavky se týkají tvaru samotné třídy. Událost je po vytvoření neměnná – veškeré properties jsou read-only, nastavené v konstruktoru. A musí jít serializovat do trvalého formátu (JSON, MessagePack…) a deserializovat zpět bez ztráty informace.
Zbylé tři míří na obsah a životní cyklus:
- schéma události se v čase vyvíjí – stará data v Event Store je třeba udržet čitelná, typicky upcastingem (transformací starých verzí na aktuální),
- název vyjadřuje fakt v minulém čase:
UserRegistered,OrderPlaced,PaymentFailed, - data musí být dost granulární na to, aby z události šel rekonstruovat stav bez přístupu k externím zdrojům.
Na rozdělení create() / fromPayload() stojí celá idempotence systému. Kdyby konstruktor
generoval eventId a occurredAt při každém vytvoření instance, dostala by tatáž uložená
událost po deserializaci nové UUID a nový čas. Tracking tabulka zpracovaných událostí by
duplicitní doručení nepoznala a rebuild projekcí by pracoval s jinými časy, než jaké platily
při zápisu. Identita události proto vzniká právě jednou – v create() – a payload ji nese
s sebou; sloupce event_id a occurred_on v tabulce slouží už jen jako indexovaná metadata.
Čas události se ukládá i parsuje v UTC explicitně. Formát payloadu offset nenese,
takže by jinak deserializace na serveru s odlišnou default timezone časy posunula.
Pro eventType() se osvědčil formát <bounded_context>.<past_tense_verb_noun>, například
ordering.order_placed nebo payment.payment_received. Tato konvence usnadňuje
routing událostí v Symfony Messenger a jejich filtrování v Event Store.
13.05 Implementace Event Store#
Event Store je append-only databázové úložiště pro všechny doménové události. Každý záznam nese jednu událost s jejím kontextem – ke kterému agregátu patří, v jaké verzi streamu a kdy nastala. Záznamy se nikdy nepřepisují ani nemažou.
Struktura tabulky Event Store
Sloupec version nese optimistic locking. Před zápisem nové události command handler
přečte poslední verzi streamu agregátu. Pokud mezitím jiný proces zapsal událost se stejnou
verzí, databáze při insertu vyvolá výjimku z porušení unikátního indexu uq_aggregate_version.
Souběžné zápisy se tak detekují bez pesimistického zamykání řádků.
Parametr :limit v metodě loadAll() má explicitní typ ParameterType::INTEGER.
Bez něj DBAL hodnotu naváže jako řetězec a MySQL výraz LIMIT '500' odmítne jako
syntaktickou chybu.
13.06 Agregát s Event Sourcingem#
V klasickém DDD agregát mění svůj stav přímou modifikací vlastních atributů. V Event Sourcingu každá změna stavu prochází přes doménovou událost. Metody agregátu nemodifikují atributy přímo – nahrají událost a teprve její aplikace na stav vyvolá změnu.
Výsledkem je, že agregát obsahuje dvě sady metod:
- Mutační metody (veřejné rozhraní agregátu) – validují invarianty, rozhodují, která událost nastane, a volají interní metodu pro nahrání události (typicky
recordEvent()). Jméno se záměrně liší odrecord()ve stavově ukládaném AggregateRoot – zde metoda událost navíc aplikuje na stav a inkrementuje verzi streamu. apply*()metody (private/protected) – přijmou konkrétní typ události a aplikují změnu na interní stav. Tyto metody jsou volány jak při nahrávání nové události, tak při replay z Event Store.
Pro testování to znamená vzor given/when/then – given jsou historické události, when je volání metody na agregátu, then jsou nově emitované události. Podrobně v kapitole Testování DDD kódu.
U event-sourced agregátu se stav rekonstruuje replayem a vnitřní properties zůstávají
privátní – metody apply* je opakovaně přepisují. Gettery na konci třídy proto
nahrazují public private(set) z kapitoly Návrh agregátu; jde
o záměrnou odchylku od konvence zbytku knihy.
Načítání agregátu z event streamu (replay)
Repozitář pro event-sourcovaný agregát neprovádí SELECT do tabulky entit. Místo toho načte
event stream z Event Store a předá jej statické tovární metodě reconstituteFromEvents().
Výsledný agregát má přesně takový stav, jaký odpovídá historii jeho událostí.
13.07 Projekce (Projections)#
Event Store je append-only a neumí ad-hoc dotazy typu „všechny objednávky zákazníka X s celkovou hodnotou nad 1000 Kč“. Pro takové dotazy vznikají vedle něj projekce – denormalizované read modely budované z event streamu specificky pro tvar dotazů aplikace.
Synchronní vs. asynchronní projekce
- Synchronní projekce – Projekce se aktualizuje přímo v téže transakci jako zápis události. Garantuje konzistenci dat v okamžiku odpovědi na command, ale zvyšuje latenci zápisu a zavádí těsnou vazbu mezi write a read stranou.
- Asynchronní projekce – Události jsou po uložení do Event Store zařazeny do fronty (Symfony Messenger + transport jako RabbitMQ nebo Redis). Projektor je konzument, který zpracovává zprávy nezávisle. Read model je v krátkém časovém okně nekonzistentní (eventual consistency), ale write side je rychlejší a oddělená.
Asynchronní doručování událostí projektorům přes Symfony Messenger vyžaduje nastavený
transport a routing v config/packages/messenger.yaml:
Projekce lze přebudovat (rebuild) přehráním celého Event Store od začátku. Při změně doménových požadavků stačí vytvořit novou projekci a přehrát historii. CRUD systémy tuto možnost nemají – historická data v nich už nejsou k dispozici.
13.08 Event Store jako outbox#
Předchozí sekce ukazovala projektory jako Messenger handlery, které dostávají doménové události z asynchronní fronty. Implicitně jsme předpokládali, že se událost po zápisu do Event Store spolehlivě dostane do message brokeru. V produkci to bez další infrastruktury neplatí. Zápis do databáze a publikace do brokeru jsou dvě nezávislé operace; spadne-li proces mezi nimi, událost je uložená, ale ke konzumentům nikdy nedorazí. Tento dual-write problem, jeho varianty i obecné řešení s kompletním kódem rozebírá kapitola Outbox Pattern. Zde jen to, co je na Event Sourcingu specifické: druhá tabulka není potřeba.
Relay čte přímo z event_store
Tabulka event_store splňuje vlastnosti outbox tabulky sama o sobě. Je to append-only
log a každý záznam vzniká ve stejné transakci jako odpovídající doménová změna. Přidává
se relay worker, který čte nové řádky podle id a posílá je do Messengeru.
Pozici posledního publikovaného řádku si ukládá do checkpoint tabulky, takže po restartu
pokračuje tam, kde skončil. Samotný checkpoint ovšem nestačí: kvůli gap problému
popsanému v následujícím calloutu se musí kombinovat s některou z mitigací – překryvem
s deduplikací, výběrem přes FOR UPDATE SKIP LOCKED, nebo CDC.
Implementace relay – polling worker pod supervisord, nebo
varianta s CDC – je shodná s běžným outboxem, viz
Relay process – dvě varianty.
Záruky doručení a jejich důsledky
Outbox dává at-least-once doručení uvnitř jednoho kanálu. Konkrétně:
- At-least-once: pokud relay spadne mezi dispatchem a updatem checkpointu, stejná událost se po restartu publikuje znovu. Konzumenti musí být idempotentní – přesně tak, jak ukazuje následující sekce u projektorů.
- Pořadí: relay publikuje vzestupně podle
id. Uvnitř streamu jednoho agregátu to odpovídá pořadí verzí, takže projektor uvidíOrderCreatedpředOrderShipped. Napříč agregáty pořadí zajištěno není a kvůli gap problému popsanému výše nejde o spolehlivé globální pořadí commitů. - Latence: mezi commitem události a jejím doručením k projektoru vzniká okno odpovídající polling intervalu relay. V praxi 100 ms až 1 s; nižší latenci dává výstupní transport, který umí push (např. PostgreSQL
LISTEN/NOTIFYnebo Debezium).
13.09 Praktické problémy projekcí#
Předchozí sekce ukázaly, jak projekci vybudovat a jak události spolehlivě doručit. V praxi se ale objevují problémy, které z jednoduchých ukázek nejsou patrné. Tato sekce pokrývá nejčastější z nich – idempotenci, chybové stavy, rebuild a eventual consistency z pohledu uživatelského rozhraní.
Idempotence projektorů
Asynchronní transport (RabbitMQ, Redis Streams, Amazon SQS) garantuje doručení zprávy alespoň jednou (at-least-once delivery). Zpráva se proto může doručit opakovaně – po timeoutu, restartu workeru nebo síťovém výpadku. Pokud projektor není idempotentní, opakované zpracování způsobí poškozená data: duplicitní řádky, zdvojené částky, nekonzistentní počty.
Idempotenci lze zajistit dvěma způsoby: upsert (INSERT … ON DUPLICATE KEY UPDATE) místo prostého INSERT, nebo tracking tabulka již zpracovaných událostí. U tracking tabulky musí záznam checkpointu a zápis projekce proběhnout v jedné databázové transakci – atomicita obou zápisů je podstatou idempotence. Pád workeru mezi nimi by jinak událost tiše ztratil.
Chybové stavy a retry strategie
Projektor může selhat z mnoha důvodů: dočasná nedostupnost databáze, neplatný payload u staré události bez upcasteru, nebo bug v projekční logice. Symfony Messenger nabízí dvě hlavní mechaniky pro řešení:
- Retry transport – zpráva se po selhání automaticky vrátí do fronty s exponenciálním backoffem (výchozí: 3 pokusy s násobičem 2).
- Failed transport (dead letter queue) – po vyčerpání retry pokusů se zpráva přesune do samostatné fronty, kde čeká na manuální zásah. Nedojde ke ztrátě události ani k zablokování zbytku fronty.
Pro diagnostiku a opětovné zpracování selhalých zpráv slouží příkazy Symfony Messenger:
bin/console messenger:failed:show– zobrazí zprávy v dead letter queuebin/console messenger:failed:retry– pokusí se zprávy znovu zpracovatbin/console messenger:failed:remove {id}– odstraní neplatnou zprávu
Rebuild projekcí
Možnost přebudovat projekci od začátku je v Event Sourcingu praktická obrana proti chybám v projekční logice. V provozu jde ale o netriviální operaci. Rebuild musí běžet odděleně od normálního provozu projektoru, stará data se musí korektně odstranit a po dokončení musí projekce odpovídat aktuálnímu stavu Event Store.
Eventual consistency a uživatelské rozhraní
Asynchronní projekce vytváří časové okno, typicky milisekundy až jednotky sekund, kdy uživatel akci provedl, ale read model ji ještě nezobrazuje. Po kliknutí na „Potvrdit“ svítí na výpisu stále „Draft“. Nejde o bug, nýbrž o vlastnost architektury. Strategie pro UI – optimistickou aktualizaci, potvrzovací stránku, polling či SSE – rozebírá sekce Eventual Consistency v praxi v kapitole CQRS.
13.10 Snapshotting#
Se stárnutím systému rostou event streamy agregátů. Agregát s tisíci událostmi vyžaduje při každém zpracování commandu načíst a přehrát celý ten objem řádků z databáze. Výkonnostní problém se v provozu objeví dřív, než tým očekává.
Vzor snapshotting uchová aktuální stav agregátu v pravidelných intervalech – po každých N událostech nebo časově. Při příštím načtení repozitář vyhledá poslední snapshot a z Event Store dotáhne jen události novější než tento snapshot.
Kdy vytvářet snapshots
- Poté, co replay agregátu začne měřitelně zpomalovat – práh závisí na doméně, typicky se pohybuje od stovek po tisíce událostí.
- Periodicky (např. jednou denně) pro agregáty s vysokou frekvencí událostí.
- Na vyžádání – jako optimalizační krok po migraci nebo importu dat.
Aby byl snapshotting funkční, musí agregát implementovat metody toSnapshot(): array
(serializace aktuálního stavu) a statickou reconstituteFromSnapshot(array $state, int $version): static
(deserializace). Na rozdíl od reconstituteFromEvents() tato metoda nevytváří apply*()
volání – přímo nastaví properties z uloženého snímku a přes restoreVersion()
z base class obnoví verzi streamu. Bez obnovené verze by optimistic locking při
prvním uložení selhal. Formát snapshotu se proto musí vyvíjet spolu s doménovým modelem.
13.11 Verzování událostí (Event Versioning)#
Události v Event Store jsou permanentní – jednou uložené zůstávají ve své podobě natrvalo. Doménový model se přitom v čase vyvíjí: přibývají atributy, mění se struktura dat, původní pole se rozdělují nebo slučují. Otázka tedy zní: jak přečíst starou událost novým kódem?
Odpověď je event versioning – strategie, která zachovává zpětnou čitelnost starých událostí i po změně jejich schématu. Nejrozšířenějším vzorem je upcasting: při deserializaci se starší verze payloadu transformuje na aktuální formát, takže doménový model pracuje pouze s nejnovější verzí.
Proč je verzování nezbytné
- Append-only princip – Události v Event Store nelze měnit. Pokud změníte schéma události, stará data zůstávají v původním formátu navždy.
- Replay a projekce – Při přebudování projekcí nebo replay agregátu se přehrávají všechny historické události, včetně těch z prvních verzí systému.
- Dlouhověkost systému – Event-sourcovaný systém může běžet roky. Za tu dobu se doménové požadavky změní mnohokrát a schémata událostí se musejí vyvíjet spolu s nimi.
Vzor Upcaster
Upcaster je objekt, který transformuje payload události z jedné verze do následující. Upcasters se řetězí: pokud existuje událost ve verzi 1 a aktuální verze je 3, proběhne transformace v1 → v2 → v3. Upcasting se provádí při čtení (deserializaci), nikoli při zápisu – původní data v Event Store zůstávají nedotčena.
Konkrétní příklad: rozdělení pole fullName
Představme si reálnou situaci: při spuštění systému událost UserRegistered obsahovala
pole fullName (celé jméno jako jeden řetězec). Později se objevil požadavek rozlišit
křestní jméno a příjmení – vznikla verze 2 se dvěma poli firstName
a lastName. V Event Store ale stále existují tisíce událostí v1 s polem fullName.
V praxi se UpcasterChain integruje do EventSerializer: při deserializaci
se z uloženého záznamu přečte event_type a schema_version, payload projde
řetězem upcasterů a teprve výsledná transformovaná data se předají konstruktoru aktuální třídy události.
Změny, které upcasting neřeší
Upcasting předpokládá, že stará data lze deterministicky přeložit na nový formát. Některé změny tuto vlastnost nemají:
- Sémantická změna pole.
Order.shippingPricepůvodně zahrnoval DPH, od v3 ho neobsahuje. Stará data nelze správně přeložit – DPH sazba v okamžiku vystavení objednávky není v eventu uložená. Upcaster může jen předpokládat (např. konstantní 21 %), což je nepřesné a generuje reporty s chybnými čísly. - Event splitting. Původní
OrderPlacedobsahovalcustomerDatainline. V nové verzi se rozděluje naOrderPlaced+CustomerSnapshotted(samostatný event). Upcaster by musel vytvořit druhý event z prvního, což porušuje princip „1 fyzický event v Event Store = 1 logický fakt“. - Event merging. Dva eventy
ItemAdded+ItemQuantityChangedse v nové doméně spojí do jednohoItemUpserted. Upcasting jdoucí jednou cestou nestačí – potřebujete agregátní transformaci napříč streamem. - Sémantický bug v doménové logice. Stará data byla validní podle starého modelu, ale ten model byl chybný. Replay přes opravený kód vyhodí výjimky.
Tři možné cesty, podle závažnosti:
Stream archivation a storage tiering
Agregáty s dlouhým životním cyklem (UserAccount, Subscription, LedgerAccount) nasbírají
za roky provozu desítky až stovky tisíc událostí. Aktivní Event Store tabulka
roste, dotazy se zpomalují a snapshoty musí vznikat častěji.
Standardní řešení: storage tiering podle stáří streamu.
- Hot tier (PostgreSQL master) – události za posledních 90 dní, dotazy < 10 ms.
- Warm tier (Postgres replika nebo tatáž databáze na pomalejším disku) – události 90 dní – 2 roky. Hydration sahá sem jen pro forenzní dotazy nebo plný replay projekce.
- Cold tier (S3, Glacier, on-prem object storage) – události starší než 2 roky. Pouze pro čtení; přístup k němu vyžadují jen auditní reporty a compliance.
Implementace: každou noc se spustí job, který
přesune event_store řádky starší než N dní do event_store_archive tabulky
(nebo přímo do S3 jako Parquet). Repozitář při hydration ve výchozím nastavení cold tier nečte – pokud agregát potřebuje plný replay, operátor jej explicitně obnoví
ze snapshotu novějšího, než je hranice cold tieru. Pro audit dotazy funguje zvlášť query
service, který umí číst všechny tři tiers.
Časté otázky
Co je Event Sourcing?
Event Sourcing je přístup k persistenci stavu, při kterém se neukládá aktuální snímek dat, ale append-only sekvence neměnných událostí, které k aktuálnímu stavu vedly. Aktuální stav agregátu vzniká přehráním těchto událostí od počátku, což poskytuje úplný audit trail a možnost zpětně rekonstruovat jakýkoli stav v čase. Platí princip „current state is derived from the history of events“: event log se pouze rozšiřuje o nové záznamy. Viz úvodní sekci.
Jaký je vztah mezi Event Sourcingem a CQRS?
Event Sourcing a CQRS jsou dva nezávislé vzory, které se často kombinují. Každý z nich lze zavést samostatně: CQRS funguje i s klasickou ORM persistencí, ES lze implementovat i bez rozdělení na write a read modely. V praxi se však hodí dohromady, protože ES přirozeně vede k oddělení zápisu (event store) a čtení (projekce do read modelů) – což je přesně myšlenka CQRS. Více v sekci Vztah k CQRS.
Co je Event Store a k čemu slouží?
Event Store je specializované append-only úložiště, které persistuje doménové události jednotlivých agregátů chronologicky seřazené. Typicky poskytuje dotazy na event stream konkrétního agregátu pro jeho rekonstrukci a globální dotaz pro čtení událostí všemi projekcemi. Základní metody jsou append(streamId, events) a readStream(streamId); pokročilejší řešení zahrnují optimistické zamykání verzí a publikování událostí do event busu. Implementačně může jít o specializovaný produkt (KurrentDB, dříve EventStoreDB), o PHP knihovnu nad relační databází (EventSauce, Broadway, prooph), nebo o vlastní minimalistickou nadstavbu, jakou staví tato kapitola. Detailní rozbor v sekci Implementace Event Store.
Co jsou projekce v Event Sourcingu?
Projekce je proces, který naslouchá událostem z event store a buduje z nich read modely – denormalizované datové struktury určené pro rychlé dotazy. Projekce bývá jednoúčelová: každý read model má obvykle vlastní projekci, která ho od začátku nebo od posledního zpracovaného offsetu udržuje aktuální. Projekce lze kdykoli přebudovat (rebuild) přehráním událostí od počátku, čímž se bezpečně opravují chyby v read modelech. Praktický příklad v sekci Projekce.
K čemu slouží snapshotting v Event Sourcingu?
Snapshotting je technika, při které se periodicky ukládá serializovaný stav agregátu, aby se při jeho rekonstrukci nemuselo přehrávat celé event history od začátku. Při načtení se vezme poslední snapshot a aplikují se pouze události, které nastaly po něm. Snapshoty řeší výkonnostní problém dlouhých streamů, typicky u agregátů s řádově tisíci událostí – pro krátké streamy jsou zbytečné a přidávají operační komplexitu. Podrobný rozbor v sekci Snapshotting.
Kdy se vyplatí Event Sourcing nasadit?
Event Sourcing se vyplatí tam, kde je historie změn sama o sobě doménově cenná – finanční systémy, sklady, auditované procesy, regulovaná odvětví – nebo kde je třeba rekonstruovat stav v libovolném bodě minulosti. Nevhodný je pro prototypy, MVP a prosté CRUD aplikace. Nasazuje se zpravidla selektivně na jeden bounded context, nikoli plošně na celou aplikaci. Rozhodovací kritéria v sekci Kdy použít Event Sourcing.