CQRS, Event Sourcing a spójność danych

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
E9
  • Rejestracja:ponad 13 lat
  • Ostatnio:12 miesięcy
  • Postów:395
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

edytowany 2x, ostatnio: error91
neves
  • Rejestracja:prawie 22 lata
  • Ostatnio:dzień
  • Lokalizacja:Kraków
  • Postów:1114
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?

Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około godziny
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
neves
  • Rejestracja:prawie 22 lata
  • Ostatnio:dzień
  • Lokalizacja:Kraków
  • Postów:1114
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?

Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około godziny
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ń.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
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?

Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około godziny
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


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
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?

Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około godziny
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)


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
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?

Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około godziny
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
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.

Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około godziny
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.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
E9
  • Rejestracja:ponad 13 lat
  • Ostatnio:12 miesięcy
  • Postów:395
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

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.