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:10 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:ponad 21 lat
  • 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:prawie 20 lat
  • Ostatnio:około 11 godzin
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:ponad 21 lat
  • 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:prawie 20 lat
  • Ostatnio:około 11 godzin
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:prawie 20 lat
  • Ostatnio:około 11 godzin
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:prawie 20 lat
  • Ostatnio:około 11 godzin
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:prawie 20 lat
  • Ostatnio:około 11 godzin
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:prawie 20 lat
  • Ostatnio:około 11 godzin
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:10 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

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)