Obsługa współbieżności ES / CQRS

20

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).

ES / CQRS

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

Louis F.
źródło
Dlaczego nie zaktualizować agregatu w kroku 4 zamiast czekać do kroku 7?
Erik Eidt
Masz na myśli, że w tym przypadku magazyn zdarzeń to tylko dziennik, który jest czytany dopiero po uruchomieniu aplikacji w celu odtworzenia agregatów / innych projekcji?
Louis F.

Odpowiedzi:

19

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).

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:

  1. polecenie dociera do modułu obsługi poleceń, tj. usługi w Application layer.
  2. moduł obsługi komend identyfikuje Aggregatei ładuje go z repozytorium (w tym przypadku ładowanie odbywa się przez new- Aggregatewystą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)
  3. handler komenda wywołuje odpowiednią metodę na Aggregate, jak Account::withdrawMoney(100)i zbiera uzyskane wydarzenia, tj MoneyWithdrewEvent(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.
  4. program obsługi poleceń próbuje utrwalić plik w Aggregaterepozytorium (w tym przypadku repozytorium to Event Store); to zrobić poprzez dodanie nowych zdarzeń do Event streamwtedy i tylko wtedy, gdy versionz Aggregatejest jeszcze jeden, który był, gdy Aggregatezostał załadowany. Jeśli wersja nie jest taka sama, polecenie jest ponawiane - przejdź do kroku 1 . Jeśli versionjest to to samo, zdarzenia są dołączane do, Event streama klient otrzymuje Successstatus.

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 streamjest abstrakcją wokół wszystkich zdarzeń, które zostały wyemitowane przez to samo Agregat.

Powinieneś zrozumieć, że Event storejest to po prostu inny rodzaj trwałości, w którym przechowywane są wszystkie zmiany w agregacie, a nie tylko stan końcowy.

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?

Sklep zdarzeń jest zawsze źródłem prawdy.

b) Zamki == impasy, czy masz wgląd w najlepsze praktyki?

Korzystając z optymistycznego blokowania, nie masz blokad, wystarczy powtórzyć polecenie.

W każdym razie, Zamki! = Zakleszczenia

Constantin Galbenu
źródło
2
Istnieją pewne optymalizacje dotyczące ładowania miejsca, w Aggregatektórym nie stosuje się wszystkich zdarzeń, ale zachowuje się migawkę tego Aggregatedo pewnego momentu w przeszłości i stosuje się tylko zdarzenia, które miały miejsce po tym punkcie.
Constantin Galbenu,
Ok, myślę, że moje zamieszanie wynika z faktu, że Event Event == Magistrala zdarzeń (mam na myśli Kafkę), dlatego przebudowa agregatu może być kosztowna, ponieważ może być konieczne ponowne przeczytanie DUŻYCH wydarzeń. W przypadku migawki 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ń?
Louis F.,
Istnieje kilka strategii tworzenia migawki. Jednym z nich jest wykonanie migawki co n zdarzeń. Powinieneś przechowywać migawkę wraz ze zdarzeniami, w tym samym miejscu / trwałości / bazie danych, na tym samym zatwierdzeniu. Chodzi o to, że migawka jest silnie związana z wersją Agregatu.
Constantin Galbenu,
Ok, myślę, że mam jaśniejszą wizję, jak sobie z tym poradzić. Teraz ostatnie pytanie, jaka jest rola magistrali eventowej? Czy agregaty są aktualizowane synchronicznie?
Louis F.,
1
Tak, możesz użyć RabbitMQ lub innego kanału, który chcesz wysyłać zdarzenia do odczytanych modeli asynchronicznie, ale dopiero po utrwaleniu ich w magazynie zdarzeń. Rzeczywiście, po utrwaleniu nie jest przeprowadzana walidacja zdarzenia: zdarzenia reprezentują fakty, które miały miejsce; czytany model może lub nie, że coś się wydarzyło, ale nie może zmienić historii.
Constantin Galbenu,
1

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).

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.

umieść identyfikator wersji zagregowanej wraz ze zdarzeniem w magazynie zdarzeń, więc jeśli podczas modyfikacji występuje niezgodność wersji, nic się nie dzieje

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.

VoiceOfUnreason
źródło
Hmm, myślę, że wtedy źle zrozumiałem składnik magazynu zdarzeń. Pomyślałem, że wszystko powinno przez to przejść i zostać strumieniowane. Co się stanie, jeśli mój sklep z wydarzeniami jest kafką i jest tylko do odczytu? Nie mogę sobie pozwolić na krok 2 i 3, aby ponownie przejrzeć wszystkie wiadomości. Wygląda na to, że ogólnie moja wizja pasowała do tej: medium.com/technology-learning/...
Louis F.