CQRS, Event Sourcing a spójność danych

0

Witam,
Zastanawiam się jak zachować spójność danych przy zastosowaniu event sourcingu, cqrs i np. Kafki.
Rozważmy 2 przypadki:
Przypadek 1:

  • Kowalski rejestruje się z pseudonimem "Foo"
  • Nowak rejestruje się z pseudonimem "Foo" w tym samym czasie
  • Każdy microserwis ma dodany unikalny index na kolumnie z pseudonimem
  • Pseudonimy służą jako login
    Jak w takiej sytuacji zapobiec rejestracji 2x użytkownika o tym samym pseudonimie? Nie mamy transakcji. Po stronie microserwisu, który odbiera event i updatuje agregat na walidacje jest już za późno, ponieważ event już się wydarzył. W momencie walidacji, przed publikacją eventu użytkownik "Foo" nie istnieje.

Przypadek 2 - nieco bardziej abstrakcyjny:

  • Losujemy UUID nowo utworzonego użytkownika na microserwisie A
  • Losujemy UUID nowo utworzonego użytkownika na microserwisie B
  • Publikujemy event z rejestracją
  • Przez przypadek został wylosowany ten sam UUID, co narusza unikalny index na każdym z agregatów.
    .
    Pozdrawiam
0

How can I make sure a newly created user has a unique user name?
This is a commonly occurring question since we're explicitly not performing cross-aggregate operations on the write side. We do, however, have a number of options:

• Create a read-side of already allocated user names. Make the client query the read-side interactively as the user types in a name.
• Create a reactive saga to flag down and inactivate accounts that were nevertheless created with a duplicate user name. (Whether by extreme coincidence or maliciously or because of a faulty client.)
• If eventual consistency is not fast enough for you, consider adding a table on the write side, a small local read-side as it were, of already allocated names. Make the aggregate transaction include inserting into that table.

Źródło -> http://cqrs.nu/Faq

0

Najłatwiej i najlepiej(zwykle) jest przesunąć odpowiedzialność za spójność gdzieś indziej:

  • niech loginem będzie adres mail, maile są unikalne
  • generowanie loginu automatycznie przez system (jannow1234)
  • wysyłamy maila postfactum, z przeprosinami, jakimś gratisem(jak to jakiś handel jest), i prośbą o podanie nowego loginu

Przypadek 2, albo jedno "źródło prawdy" do generowania UUID, albo tak napisać algorytm żeby nie było możliwości kolizji.

0

Okej.
Weźmy przypadek 1. Event zostanie opublikowany, mamy 20 agregatów z bazami do odczytu. 20 agregatów będzie miało problem ze spójnością danych. Chcemy wysłać email do usera że coś poszło nie tak. Jak zrobić to tak, by nie dostał 20 emaili - po 1 z każdego agregatu?

0
Nadziany Kret napisał(a):

Witam,
Zastanawiam się jak zachować spójność danych przy zastosowaniu event sourcingu, cqrs i np. Kafki.
Rozważmy 2 przypadki:
Przypadek 1:

  • Kowalski rejestruje się z pseudonimem "Foo"
  • Nowak rejestruje się z pseudonimem "Foo" w tym samym czasie
  • Każdy microserwis ma dodany unikalny index na kolumnie z pseudonimem
  • Pseudonimy służą jako login
    Jak w takiej sytuacji zapobiec rejestracji 2x użytkownika o tym samym pseudonimie? Nie mamy transakcji. Po stronie microserwisu, który odbiera event i updatuje agregat na walidacje jest już za późno, ponieważ event już się wydarzył. W momencie walidacji, przed publikacją eventu użytkownik "Foo" nie istnieje.

Zdarzenie jest generowane po przetworzeniu polecenia, a przetwarzanie polecenia pociąga za sobą walidację. Ponadto polecenia dla pojedynczego agregatu powinny być przetwarzane sekwencyjnie. Przynajmniej takie podejście do sprawy wydaje mi się sensowne. W tym przypadku agregatem byłby użytkownik (jego dane) identyfikowany przez login i polecenia rejestracji wpadłyby do jednego agregatu. W Akce obsługą pojedynczej instancji agregatu może zajmować się aktor. Wtedy procedura rejestracji wygląda tak

  • użytkownik próbuje się rejestrować
  • polecenie rejestracji wpada do mikroserwisu
  • z polecenia wyciągamy login i szukamy bądź tworzymy aktora o nazwie odpowiadającej loginowi
  • aktor ten odpowiada na emitowanie zdarzeń w reakcji na polecenia
  • polecenia przychodzą do niego sekwencyjnie
  • jeśli przyjdą do niego dwa polecenia rejestracji to jedno się nie powiedzie

Takie wnioski wyciągam po rozmowie z kolegą z zespołu nt CQRS i ES oraz po przeczytaniu artykułu http://www.strongtyped.io/blog/2017/05/07/building-cqrs-es-framework-part1/ , a szczególnie akapitu:

That may sound surprising, but the sole purpose of the Command Side is to offer the means to identify what are the possible next events for a given Aggregate instance. It’s not about querying the state nor about a fancy CRUD model. It’s only about agumenting the History. That said, the only reason why we should bring an Aggregate into memory is to decide which Events may be emitted next, nothing else.

Dokumentacja Akki w kontekście używania aktorów do implementacji Event Sourcingu: http://doc.akka.io/docs/akka/current/scala/persistence.html

Nadziany Kret napisał(a):

Przypadek 2 - nieco bardziej abstrakcyjny:

  • Losujemy UUID nowo utworzonego użytkownika na microserwisie A
  • Losujemy UUID nowo utworzonego użytkownika na microserwisie B
  • Publikujemy event z rejestracją
  • Przez przypadek został wylosowany ten sam UUID, co narusza unikalny index na każdym z agregatów.
    .
    Pozdrawiam

Abstrahując od używania UUIDów do nazywania nowo utworzonych kont to kolizje UUIDów można traktować jako coś wyjątkowego i wtedy po prostu poprosić użytkownika o ponowienie akcji.

1
Nadziany Kret napisał(a):

Okej.
Weźmy przypadek 1. Event zostanie opublikowany, mamy 20 agregatów z bazami do odczytu. 20 agregatów będzie miało problem ze spójnością danych. Chcemy wysłać email do usera że coś poszło nie tak. Jak zrobić to tak, by nie dostał 20 emaili - po 1 z każdego agregatu?

Tak jak Wibowit wyżej napisał, zdarzenia są generowane po wykonaniu komendy, i są przetwarzane sekwencyjnie jeśli jest taka potrzeba (nie zawsze jest, czasami mamy prawdziwie bezstanowe operacje które możemy wykonywać równolegle). Sekwencyjność osiąga się przez użycie tylko jednej instancji danego agregatu, albo poprzez dzielenie przez te instancje wspólnego źródła danych(prawdy). Także dostały się do naszego systemu dwie komendy RegisterUser("JanNowak") i RegisterUser("JanNowak"), pierwsza która trafi do agregatu zostanie przetworzona pomyślnie i wygeneruje event UserRegistered na który zareaguje te 20 agregatów, a druga która dotrze wygeneruje event RegistrationFailed na który zareaguje serwis wysyłający maile, i jakiś agregat do zapisania tymczasowo juz wprowadzonych danych przez użytkownika.

0

W takim razie. W jaki sposób mogę skalować microserwis do rejestracji?

0

Na moją intuicję to jeżeli np chcesz mieć 5 instancji mikroserwisu obsługującego informacje o użytkownikach to możesz policzyć hasha z loginu i podzielić go modulo przez 5. Wtedy otrzymasz numer instancji z którą trzeba się skontaktować by obsłużyć polecenie. To jedno z możliwych rozwiązań.

0

Skoro microserwis przetwarzający komendy i publikujacy eventy nie może odczytywać niczego z read modelu to w jaki sposób mogę sprawdzić czy user ma wymaganą grupę, albo czy istnieje post do którego aktualnie dodawany jest komentarz?

1

Kę? CQRS jak sama nazwa wskazuje to rozdzielenie komend i zapytań. Jeśli wydajesz polecenie to nie dostajesz żadnej odpowiedzi oprócz (opcjonalnie) takiej czy przetwarzanie polecenia się powiodło.

No chyba, że pytasz o co innego. Zarówno read side jak i write side mogą mieć swój stan. Stan strony zapisującej zawiera tylko tyle informacji by być w stanie poprawnie zareagować na polecenia i wygenerować kolejne zdarzenia. Stan strony odczytującej natomiast zawiera tyle informacji by odpowiedzieć na zapytania. Te stany na pewno będą się trochę pokrywać w sensie struktur danych. W uproszczonym modelu CQRS można nawet zastosować jedną bazę danych dla obu stron - odczytującej i zapisującej. Jednak pełny CQRS jest wtedy, gdy strona zapisująca ma własną bazę danych, a wyemitowane zdarzenia są obsługiwane także przez stronę odczytującą, która wtedy aktualizuje swoją bazę danych. Strona zapisująca powinna mieć bazę dla szybkich zapisów, np Cassandrę, a strona odczytująca może mieć nawet kilka baz danych - jakąś SQLową do robienia skomplikowanych zapytań, Apache Sparka do data miningu, ElasticSearch do wyszukiwania danych, etc

W CQRS jest eventual consistency (czyli ostateczna/ opóźniona spójność), a więc jeśli wyślesz polecenie do strony zapisującej i nawet otrzymasz potwierdzenie przetworzenia to przez pewien czas zapytania do strony odczytującej będą dawać przestarzałe dane. Dzieje się tak dlatego, że strona zapisująca za skończoną pracę uznaje zapis zdarzeń do własnej bazy danych. Wysyłanie zdarzeń do strony odczytującej odbywa się asynchronicznie. Strona odczytująca otrzymuje zdarzenie, aktualizuje własną bazę, a potem odpowiada nowymi danymi na kolejne zapytania.

Strona odczytująca i strona zapisująca znajdują się w jednym mikroserwisie. Mogą mieć po jednej instancji albo więcej. Poczytaj sobie ten artykuł: https://www.oreilly.com/ideas/the-evolution-of-scalable-microservices

0

Dopuszczalna jest sytuacja w której mamy microserwis napisany z wykorzystaniem CQRS i EventSourcingu (np. Authentication Service) i inne serwisy pytają się go po RPC lub innym protokole o zwrócenie detali użytkownika jak np. Role na podstawie tokenu które dostały? Chodzi mi tutaj głównie o brak konieczności duplikowania logiki. Czy Według artykułu podesłanego przez @Wibowit nie będzie to architektura microlitów? Chyba nie skoro Authentication Service będzie skalowalny, tak?

0

Każdy mikroserwis jest właścicielem swoich danych. Odpytywanie wspólnej bazy byłoby łamaniem zasady niezależności mikroserwisów. Przykład na rejestrowanie użytkowników z CQRS/ ES jest tutaj: https://github.com/lagom/online-auction-java (uwaga: dość nakomplikowane)

0

Nie chodzi o odpytywanie wspólnej bazy, tylko publicznego api innego microserwisu :) a nawet samego load balancera, by nie ingerować w to który dokładnie microserwis obsłuży request.
Sytuacja w której każdy microserwis musiałby nasłuchiwać eventow rejestracji, nadawania ról itp. tak by trzymać to w swojej bazie jest Ok?

0

Po co każdy mikroserwis miałby nasłuchiwać zdarzeń o rejestracji użytkowników? Oczywiście odpowiedź typu "po to żeby wiedzieć" jest niewystarczająca.

0

Żalozmy ze mamy microserwis do postów na blogu. Gdy appka dajmy na to w angularze wysyła requesta tworzącego nowy wpis. Do requesta załączony jest token. Microserwis potrzebuje wiedzieć czy użytkownik ma uprawnienia do tworzenia wpisów. Dlatego potrzebuje dostać profil usera a wraz z nim jego role na podstawie tokenu który dostała.

0

Jeden mikroserwis może odpytywać inne mikroserwisy by obsłużyć zapytanie.

Tym jak podzielić dane zajmuje się DDD. Każdy mikroserwis to Bounded Context. Stawiam, że jest wiele sensownych podziałów i dużo zależy od tego do czego zmierzasz. Nie mam jednak doświadczenia z DDD/ CQRS/ ES więc wolę się wstrzymać ze szczegółowymi poradami.

1
Nadziany Kret napisał(a):

Żalozmy ze mamy microserwis do postów na blogu. Gdy appka dajmy na to w angularze wysyła requesta tworzącego nowy wpis. Do requesta załączony jest token. Microserwis potrzebuje wiedzieć czy użytkownik ma uprawnienia do tworzenia wpisów. Dlatego potrzebuje dostać profil usera a wraz z nim jego role na podstawie tokenu który dostała.

jest coś takiego jak JWT. Uprawnienia siedzą już w tokenie, więc nie musisz pytać o nic bazy. Auth service nadaje token użytkownikowi a on już do innych serwisów się nim autoryzuje. W każdym serwisie potrzebujesz klucza, którym token został podpisany.

0

Wydaje jednak mi się że komunikacja, jaką ja chce zaimplementować jest raczej nie wskazana.
https://www.innoq.com/en/blog/why-restful-communication-between-microservices-can-be-perfectly-fine/
@error91 wiem jak działa JWT i rozumiem że w tej sytuacji jest idealne do wykorzystania, ale chodzi mi o sytuacje analogiczną. Myślę że ta z tokenem jest najprostszym przykładem potrzeby komunikacji między serwisami

1 użytkowników online, w tym zalogowanych: 0, gości: 1