Ten wzorzec zmienia sposób myślenia o stanie aplikacji: zamiast nadpisywać rekord „na dziś”, zapisujesz kolejne fakty, które doprowadziły system do obecnego punktu. W praktyce event sourcing sprawdza się tam, gdzie liczy się historia zmian, audyt, odtwarzanie stanu i kilka różnych widoków danych z jednej osi zdarzeń. To dobry temat dla zespołów, które projektują systemy w obszarze płatności, rezerwacji, logistyki albo złożonych workflow.
Najważniejsze rzeczy do zapamiętania o modelu opartym na zdarzeniach
- Nie zapisujesz tylko stanu końcowego - przechowujesz sekwencję faktów biznesowych, z których stan da się odtworzyć.
- Historia jest częścią systemu - audyt, analiza błędów i odtwarzanie stanu są prostsze niż w klasycznym CRUD.
- Najczęściej potrzebujesz osobnych widoków odczytu - projekcje i materialized views przyspieszają zapytania.
- To rozwiązanie zwiększa złożoność - trzeba pilnować kolejności, idempotencji, wersjonowania i kosztu replay.
- Najlepiej działa przy złożonej domenie - zwłaszcza tam, gdzie biznes chce wiedzieć nie tylko co jest teraz, ale też jak do tego doszło.
Jak działa model oparty na zdarzeniach
Najprościej ująć to tak: użytkownik albo inny system wysyła komendę, logika domenowa sprawdza reguły, a wynikowa zmiana trafia do systemu jako zdarzenie. Zdarzenie opisuje fakt, a nie życzenie - przykładowo „zamówienie opłacone”, „rezerwacja anulowana” albo „produkt przeniesiony do innego magazynu”.
- Komenda trafia do logiki domenowej - na przykład „zapłać za zamówienie”.
- System waliduje reguły - sprawdza, czy zamówienie istnieje, czy nie zostało już opłacone i czy transakcja może przejść dalej.
- Powstaje zdarzenie - zapisujesz fakt biznesowy, który już się wydarzył.
- Stan jest odtwarzany z historii - aplikacja może przejść przez sekwencję zdarzeń i zrekonstruować aktualny obraz danych.
To właśnie historia zdarzeń jest źródłem prawdy. Odczyt aktualnego stanu to wynik odtworzenia tej historii, czyli rehydracji. Dzięki temu możesz sprawdzić nie tylko, co jest teraz w systemie, ale też jak do tego doszło.
W praktyce daje to dużą przewagę tam, gdzie incydentów i zmian jest dużo, a biznes chce rozumieć cały przebieg procesu. Gdy ten obraz jest już jasny, naturalnie pojawia się pytanie: gdzie taki model naprawdę się opłaca, a gdzie tylko dokłada pracy.
Gdzie ten wzorzec daje największą przewagę
Ja zwykle traktuję ten model jako narzędzie do zadań specjalnych. Najmocniej błyszczy tam, gdzie historia nie jest dodatkiem, tylko częścią produktu albo wymagań operacyjnych.
- Systemy z audytem i zgodnością - jeśli musisz pokazać, kto, kiedy i dlaczego zmienił stan, sekwencja faktów jest dużo wygodniejsza niż pojedynczy rekord końcowy.
- Procesy z wieloma krokami biznesowymi - zamówienia, płatności, zwroty, reklamacje czy rezerwacje łatwiej rozłożyć na zdarzenia niż na jeden „stan obiektu”.
- Analiza historyczna i raportowanie - możesz odtworzyć stan systemu z konkretnego momentu, zamiast polegać na archiwach, logach aplikacyjnych i ręcznych rekonstrukcjach.
- Wiele widoków danych z jednego źródła - panel operacyjny, raport finansowy i widok dla supportu mogą bazować na tych samych zdarzeniach, ale prezentować dane inaczej.
- Środowiska, w których liczy się wyjaśnialność - łatwiej odpowiedzieć na pytanie „dlaczego system podjął taką decyzję?”, bo masz pełen ciąg faktów, a nie tylko aktualny zapis.
To nie znaczy, że każdy projekt powinien iść tą drogą. Jeśli aplikacja ma głównie prosty formularz CRUD, bez potrzeby odtwarzania historii, model zdarzeniowy może być zwyczajnie zbyt ciężki. I właśnie dlatego warto zestawić go z prostszymi podejściami, zanim podejmiesz decyzję architektoniczną.

Jak różni się od CRUD i CQRS
Najwięcej nieporozumień bierze się stąd, że te podejścia rozwiązują podobny problem, ale z zupełnie inną filozofią. CRUD skupia się na aktualnym stanie rekordu. CQRS rozdziela zapis i odczyt. Model oparty na zdarzeniach idzie krok dalej i zapisuje nie stan, tylko chronologiczny zapis zmian.
| Kryterium | CRUD | CQRS | Model oparty na zdarzeniach |
|---|---|---|---|
| Co jest źródłem prawdy | Aktualny rekord w bazie | Osobny zapis i osobny odczyt | Strumień zdarzeń |
| Historia zmian | Zwykle ograniczona lub brak | Zależy od implementacji | Pełna i jawna |
| Odczyty | Bezpośrednio z tabel | Z modelu odczytu | Z projekcji lub materialized views |
| Złożoność | Niska | Średnia | Wysoka |
| Kiedy zwykle wygrywa | Proste systemy CRUD | Gdy odczyt i zapis mają różne wymagania | Gdy historia, audyt i replay są ważne biznesowo |
Warto też pamiętać, że CQRS nie jest obowiązkowe, choć często idzie w parze z tym podejściem. Możesz mieć model zdarzeniowy bez pełnego rozdzielenia na write model i read model, ale w praktyce osobne projekcje bardzo pomagają, gdy system zaczyna rosnąć. To prowadzi do najważniejszej części: jak wdrożyć to tak, żeby historia nie zamieniła się w techniczny bałagan.
Jak wdrożyć to bez psucia historii
Zacznij od granic agregatu
Najpierw ustal, co jest jedną spójną jednostką biznesową, czyli agregatem. Jeśli zbyt dużo rzeczy wrzucisz do jednego agregatu, dostaniesz trudne blokady współbieżności, ciężki replay i model, którego nikt nie będzie chciał dotykać. Lepszy jest mniejszy, wyraźny zakres odpowiedzialności niż pozorna „pełna spójność” wszystkiego naraz.
Projektuj zdarzenia jak fakty biznesowe
Zdarzenie powinno opisywać coś, co naprawdę zaszło w domenie, a nie techniczny krok implementacyjny. „Płatność zaakceptowana” ma wartość biznesową. „Zaktualizowano pole status” już nie. To ważne, bo takie komunikaty żyją długo i stają się publicznym kontraktem dla innych części systemu.
Oddziel zapis od odczytu i zaakceptuj opóźnienie
Po zapisie zdarzenia widok odczytu nie musi być natychmiast gotowy. Często buduje się go asynchronicznie jako projekcję, czyli osobny model pod zapytania. To oznacza eventual consistency - przez chwilę odczyt może być „w drodze”, ale zyskujesz skalowalność i prostsze modelowanie złożonych zapytań.
Przeczytaj również: Git branch, switch czy checkout? Twórz gałęzie świadomie!
Dodaj snapshoty, idempotencję i wersjonowanie
Snapshot to zapis stanu w danym punkcie historii, który skraca odtwarzanie długich strumieni zdarzeń. Nie używaj go wszędzie, ale rozważ go tam, gdzie replay robi się kosztowny. Równie ważna jest idempotencja, czyli odporność na wielokrotne przetworzenie tego samego zdarzenia. Bez niej duplikaty potrafią rozjechać projekcje, a czasem nawet uruchomić skutki uboczne dwa razy.
Do tego dochodzi wersjonowanie zdarzeń. Nie chcesz przecież przepisywać historii za każdym razem, gdy zmieni się struktura domeny. Lepsze jest świadome rozwijanie schematu: nowe wersje komunikatów, upcasting albo warstwa adaptacji, która potrafi czytać starsze formaty. To właśnie te detale decydują, czy system będzie stabilny po kilku miesiącach, czy po kilku sprintach.
Najczęstsze pułapki, które widać dopiero na produkcji
Na papierze wszystko wygląda elegancko. Problemy zaczynają się wtedy, gdy historia rośnie, zespół zmienia model, a odczyty mają działać szybko mimo coraz większej liczby zdarzeń. Tu właśnie najłatwiej popełnić błędy, których nie widać przy pierwszej implementacji.
| Błąd | Co psuje | Jak temu przeciwdziałać |
|---|---|---|
| Zbyt techniczne zdarzenia | Trudno je zrozumieć biznesowo i trudno utrzymać kontrakt | Opisuj fakty domenowe, nie wewnętrzne kroki kodu |
| Próba odtwarzania wszystkiego od zera przy każdym odczycie | Rosną opóźnienia i koszty obliczeń | Stosuj snapshoty i projekcje tam, gdzie to ma sens |
| Zmiana starych zdarzeń „w miejscu” | Łamiesz historię i ryzykujesz niespójność analiz | Wersjonuj zdarzenia i wprowadzaj kompatybilne formaty |
| Brak kontroli duplikatów | Projekcje i efekty uboczne zaczynają się rozjeżdżać | Stosuj identyfikatory zdarzeń, numery sekwencyjne i idempotentne handlery |
| Za szerokie agregaty | Współbieżność staje się problemem, a replay jest ciężki | Rozdzielaj odpowiedzialności i pilnuj granic modelu |
W praktyce najdroższy błąd to nie sama technologia, tylko zbyt szybkie uznanie, że „skoro już mamy zdarzenia, to problem rozwiązany”. Nie, nie jest rozwiązany. Zmieniasz tylko miejsce, w którym trzeba myśleć o spójności, wersjach i odczytach. I właśnie dlatego przed wdrożeniem warto przejść przez prostą, ale bezlitosną checklistę.
Co sprawdzić przed decyzją o wdrożeniu
- Czy historia zmian ma realną wartość dla biznesu, audytu albo supportu?
- Czy musisz odtwarzać stan z konkretnego momentu w czasie?
- Czy odczyty będą się różnić od zapisu na tyle mocno, że osobne projekcje mają sens?
- Czy zespół umie pracować z eventami, idempotencją i eventual consistency?
- Czy akceptujesz większą złożoność w zamian za lepszą obserwowalność i replay?
- Czy prostszy model nie rozwiąże problemu szybciej i taniej?
Jeśli na większość z tych pytań odpowiadasz twierdząco, model oparty na zdarzeniach może dać Twojej aplikacji dużą przewagę: lepszy audyt, łatwiejsze odtwarzanie błędów i większą elastyczność rozwoju. Jeśli nie, prostszy CRUD zwykle będzie rozsądniejszy, szybszy do zbudowania i łatwiejszy do utrzymania. Ja w takich decyzjach trzymam się jednej zasady: nie wybieram złożoności dla samej elegancji architektury, tylko wtedy, gdy historia naprawdę staje się częścią produktu.