Niedawno zacząłem nurkować w CQRS / ES, ponieważ może być konieczne zastosowanie go w pracy. W naszym przypadku wydaje się to bardzo obiecujące, ponieważ rozwiązałoby wiele problemów.
Naszkicowałem swoje przybliżone zrozumienie tego, jak aplikacja ES / CQRS powinna wyglądać w kontekście w uproszczonej bankowości (wypłata pieniędzy).
Podsumowując, jeśli osoba A wypłaci trochę pieniędzy:
- wydano polecenie
- polecenie zostaje przekazane do weryfikacji / weryfikacji
- zdarzenie jest wypychane do magazynu zdarzeń, jeśli sprawdzanie poprawności się powiedzie
- agregator anuluje zdarzenie, aby zastosować modyfikacje agregatu
Z tego, co zrozumiałem, dziennik zdarzeń jest źródłem prawdy, ponieważ jest to dziennik FAKTÓW, możemy zatem z niego wyprowadzić każdą projekcję.
Teraz, czego nie rozumiem, w tym wielkim schemacie rzeczy, dzieje się w tym przypadku:
- reguła: saldo nie może być ujemne
- osoba A ma saldo 100e
- osoba A wydaje Wycofanie Komendy o wartości 100e
- sprawdzanie poprawności mija i następuje wydarzenie MoneyWithdrewEvent of 100e
- w międzyczasie osoba A wydaje kolejną komendę Wycofania o wartości 100e
- pierwszy MoneyWithdrewEvent nie został jeszcze zagregowany, dlatego też sprawdzanie poprawności przebiega pomyślnie, ponieważ sprawdzanie poprawności względem zagregowanego (które nie zostało jeszcze zaktualizowane)
- MoneyWithdrewEvent of 100e jest emitowany innym razem
==> Jesteśmy w niespójnym stanie równowagi na poziomie -100e, a dziennik zawiera 2 MoneyWithdrewEvent
Jak rozumiem, istnieje kilka strategii radzenia sobie z tym problemem:
- a) umieść identyfikator wersji zagregowanej wraz ze zdarzeniem w magazynie zdarzeń, więc jeśli podczas modyfikacji wystąpi niezgodność wersji, nic się nie stanie
- b) zastosować pewne strategie blokowania, co oznacza, że warstwa weryfikacyjna musi jakoś je utworzyć
Pytania dotyczące strategii:
- a) W takim przypadku dziennik zdarzeń nie jest już źródłem prawdy, jak sobie z tym poradzić? Wróciliśmy również do klienta OK, chociaż zezwolenie na wypłatę było całkowicie błędne, czy w takim przypadku lepiej jest użyć zamków?
- b) Zamki == impasy, czy masz wgląd w najlepsze praktyki?
Ogólnie, czy rozumiem, jak radzić sobie z współbieżnością?
Uwaga: Rozumiem, że ta sama osoba wypłacająca dwa razy pieniądze w tak krótkim czasie jest niemożliwa, ale wziąłem prosty przykład, aby nie zagubić się w szczegółach
źródło
Odpowiedzi:
Jest to idealny przykład aplikacji opartej na zdarzeniach. Zaczynajmy.
Za każdym razem, gdy polecenie jest przetwarzane lub ponawiane (zrozumiesz, bądź cierpliwy), wykonywane są następujące kroki:
Application layer
.Aggregate
i ładuje go z repozytorium (w tym przypadku ładowanie odbywa się przeznew
-Aggregate
wystąpienie, pobranie wszystkich wcześniej emitowanych zdarzeń tego agregatu i ponowne zastosowanie ich do samego agregatu; wersja agregacji jest przechowywana dla później użyć; po zastosowaniu zdarzeń agregat jest w stanie końcowym - tzn. saldo rachunku bieżącego jest obliczane jako liczba)Aggregate
, jakAccount::withdrawMoney(100)
i zbiera uzyskane wydarzenia, tjMoneyWithdrewEvent(AccountId, 100)
; jeśli na koncie nie ma wystarczającej ilości pieniędzy (saldo <100), zgłaszany jest wyjątek i wszystko zostaje przerwane; w przeciwnym razie wykonywany jest następny krok.Aggregate
repozytorium (w tym przypadku repozytorium toEvent Store
); to zrobić poprzez dodanie nowych zdarzeń doEvent stream
wtedy i tylko wtedy, gdyversion
zAggregate
jest jeszcze jeden, który był, gdyAggregate
został załadowany. Jeśli wersja nie jest taka sama, polecenie jest ponawiane - przejdź do kroku 1 . Jeśliversion
jest to to samo, zdarzenia są dołączane do,Event stream
a klient otrzymujeSuccess
status.Ta kontrola wersji nazywa się blokowaniem optymistycznym i jest ogólnym mechanizmem blokującym. Innym mechanizmem jest pesymistyczne blokowanie, gdy inne zapisy są blokowane (jak nie zostały uruchomione), dopóki bieżący się nie zakończy.
Termin
Event stream
jest abstrakcją wokół wszystkich zdarzeń, które zostały wyemitowane przez to samo Agregat.Powinieneś zrozumieć, że
Event store
jest to po prostu inny rodzaj trwałości, w którym przechowywane są wszystkie zmiany w agregacie, a nie tylko stan końcowy.Sklep zdarzeń jest zawsze źródłem prawdy.
Korzystając z optymistycznego blokowania, nie masz blokad, wystarczy powtórzyć polecenie.
W każdym razie, Zamki! = Zakleszczenia
źródło
Aggregate
którym nie stosuje się wszystkich zdarzeń, ale zachowuje się migawkę tegoAggregate
do pewnego momentu w przeszłości i stosuje się tylko zdarzenia, które miały miejsce po tym punkcie.Aggregate
, kiedy należy zaktualizować migawkę? Czy magazyn migawek jest taki sam jak magazyn zdarzeń, czy jest to zmaterializowany widok pochodzący z magistrali zdarzeń?Blisko. Problem polega na tym, że logika aktualizowania „agregatu” znajduje się w dziwnym miejscu.
Bardziej typową implementacją jest to, że model danych przechowywany przez moduł obsługi poleceń w pamięci oraz strumień zdarzeń w magazynie zdarzeń są synchronizowane.
Łatwy do opisania przykład to przypadek, w którym moduł obsługi poleceń zapisuje synchronicznie w magazynie zdarzeń i aktualizuje swoją lokalną kopię modelu, jeśli połączenie ze sklepem zdarzeń wskazuje, że zapis się powiódł.
Jeśli moduł obsługi poleceń musi ponownie zsynchronizować się ze składnicą zdarzeń (ponieważ jego model wewnętrzny nie odpowiada modelowi ze sklepu), robi to, ładując historię ze sklepu i odbudowując własny stan wewnętrzny.
Innymi słowy, strzałki 2 i 3 (jeśli są obecne) normalnie byłyby połączone ze sklepem zdarzeń, a nie ze sklepem zbiorczym.
Odmiany tego są zwykłym przypadkiem - zamiast dołączać do strumienia w strumieniu zdarzeń, zwykle PUT umieszczamy w określonej lokalizacji w strumieniu; jeśli operacja ta jest niezgodna ze stanem sklepu, zapis nie powiedzie się, a usługa może wybrać odpowiedni tryb awarii (nie można klienta, ponowić próby, scalić ....). Korzystanie z zapisu idempotentnego rozwiązuje wiele problemów związanych z rozproszonym przesyłaniem wiadomości, ale oczywiście wymaga posiadania sklepu obsługującego zapis idempotentny.
źródło