Event sourcing, CQRS, a do tego message brokers

Event sourcing, CQRS, a do tego message brokers
LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

Cześć, ostatnio natchnęło mnie na poznanie tych dwóch podejść architektonicznych i jak zauważyłem często idą one ze sobą w parze. No i natknęło mnie na wiele pytań, czyli:

  1. Rozumiem, że przy połączeniu tego mamy dwie bazy: Command (Event Store(?)) oraz Query (gdzie trzymamy jedynie obecny stan), tak? W takim razie jeśli Command nam produkuje event to powoduje jednocześnie zapis do bazy Query. No i tutaj na obecnym stanie w bazie Query leci UPDATE czy INSERT, który historyzuje poprzedni stan, a nowy ustawia jako aktualny? Czy może wydzielić to w ogóle na 3 story (Command, Query, Event?). W dodatku ten nowy stan tworzymy na podstawie eventów czy na podstawie obecnego stanu? Przykład: konto bankowe ma 4500 zł, przychodzi event DepositMoney i teraz bierzemy obecny stan i dodajemy do niego value tego eventu (np. 500 zł?)
  2. Rozumiem, że taki event sourcing pozwala nam na to, że potrafimy odtworzyć każdy stan z przeszłości wskazując na odpowiedni event. W takich domyślnych javowych aplikacjach enterprise używamy do tego jakichś gotowców pokroju Axona lub Spring Application Events? Czy raczej pisze się takie coś samemu? Czy możemy np. do tego użyć samemu Kafki i np. w jakiś sposób persystować nasze eventy Kafkowe (tutaj pytanie jak? Jakiś NoSQL? Redis?).
  3. Czy lepiej wydzielić do tego odpowiednie narzędzia czyli Springowe Eventy/Axon? Jeśli tak to tutaj też jak najlepiej przechowywać te eventy? Natomiast Kafki używać tylko do sytuacji pokroju register -> sendConfirmationEmail gdzie register wysyła message, który mówi żeby wysłać email przez sendConfirmationEmail dając mu jako message ten obiekt? Ogólnie do jakich zadań w biznesowych aplikacjach stosujemy message brokery pokroju Rabbita czy Kafki (ujednolicam tutaj, wiem że różnice między tymi dwoma narzędziami są i nie do końca stosujemy je do tych samych sytuacji).

Pytam, bo technologicznie jestem zacofany w mojej pracy i planuję zmiany, a chcę się zorientować w tym temacie, bo wydaje się dość ciekawy :'(.
Jeśli możecie to podrzućcie przy okazji jakieś ciekawe prelekcje/książki/artykuły w tej tematyce.

mad_penguin
mad_penguin
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Rzeszów
1

Command wyraża intencję wykonania jakiejś operacji, a przy wykonywaniu rozgłasza eventy, które mówią o tym, co zaszło. Command może być zależny od obecnego stanu, zewnętrznych systemów itp. i nie ma sensu go zapisywać, przykład:
Command: wymień 100 EUR -> PLN
Event: wymieniono 100 EUR na PLN po kursie 4,42

Eventy zapisujesz w event storze i tak naprawdę to wystarczy, jeśli nie stanowi to problemu wydajnościowego to queries mogą czytać prosto z event stora, a dodatkowe read modele możesz dodawać w ramach potrzeb. Wtedy one nasłuchują eventów i aktualizują się na ich podstawie.

W dodatku ten nowy stan tworzymy na podstawie eventów czy na podstawie obecnego stanu? Przykład: konto bankowe ma 4500 zł, przychodzi event DepositMoney i teraz bierzemy obecny stan i dodajemy do niego value tego eventu (np. 500 zł?)

Po co za każdym razem czytać poprzednie eventy skoro wystarczy zrobić jeden UPDATE?

Co do kolejek, javy itp. się nie wypowiem, bo nie jestem javowcem ani nie robiłem ES na produkcji. Zwrócę tylko uwagę, że event sourcing dotyczy tylko tego, jak wygląda główne źródło prawdy dla aplikacji i nie ma to nic wspólnego z kolejkami, przesyłaniem pomiędzy mikroserwisami itp.

LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

Command wyraża intencję wykonania jakiejś operacji, a przy wykonywaniu rozgłasza eventy, które mówią o tym, co zaszło. Command może być zależny od obecnego stanu, zewnętrznych systemów itp. i nie ma sensu go zapisywać, przykład:
Command: wymień 100 EUR -> PLN
Event: wymieniono 100 EUR na PLN po kursie 4,42

Czyli przy komendzie rozgłaszamy eventy, które zapisujemy do event store, a command nie jest w zasadzie w ogóle persystentne?

Eventy zapisujesz w event storze i tak naprawdę to wystarczy, jeśli nie stanowi to problemu wydajnościowego to queries mogą czytać prosto z event stora, a dodatkowe read modele możesz dodawać w ramach potrzeb. Wtedy one nasłuchują eventów i aktualizują się na ich podstawie.

No właśnie, czy takie Query prosto z event store nie jest dość powolne? No, bo tutaj w tym przypadku jeśli nie trzymamy nigdzie stricte stanu naszej encji to musimy zaciągnąć wszystkie eventy i na ich podstawie uzyskiwać obecny stan? Czy może tworzy się do tego wtedy jakieś Cache, które pozwalają nam np. ogarnąć stan naszej encji przy danym evencie w taki sposób żebyśmy nie musieli zaciągać całej historii, a jedynie od danego eventu?

Po co za każdym razem czytać poprzednie eventy skoro wystarczy zrobić jeden UPDATE?

Czyli jeśli mamy taki read model pod Query to tam mutujemy stan przez UPDATE na naszej stronie READ, tak? No tylko teraz to nie robi nam problemu dwóch źródeł prawdy? Co jeśli pójdzie event do event store, a insert do naszego read modelu się nie powiedzie?

Co do kolejek, javy itp. się nie wypowiem, bo nie jestem javowcem ani nie robiłem ES na produkcji. Zwrócę tylko uwagę, że event sourcing dotyczy tylko tego, jak wygląda główne źródło prawdy dla aplikacji i nie ma to nic wspólnego z kolejkami, przesyłaniem pomiędzy mikroserwisami itp.

Tak, wiem, wiem że kolejkowanie/komunikacja między mikroserwisami to jest inna bajka niż ES, jednak wolałem od razu tutaj też o to zapytać :D

mad_penguin
mad_penguin
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Rzeszów
1
lavoholic napisał(a):

Czyli przy komendzie rozgłaszamy eventy, które zapisujemy do event store, a command nie jest w zasadzie w ogóle persystentne?

W zasadzie tak.

No właśnie, czy takie Query prosto z event store nie jest dość powolne? No, bo tutaj w tym przypadku jeśli nie trzymamy nigdzie stricte stanu naszej encji to musimy zaciągnąć wszystkie eventy i na ich podstawie uzyskiwać obecny stan? Czy może tworzy się do tego wtedy jakieś Cache, które pozwalają nam np. ogarnąć stan naszej encji przy danym evencie w taki sposób żebyśmy nie musieli zaciągać całej historii, a jedynie od danego eventu?

Jeśli query dotyczy jednej encji, która ma kilkanaście czy nawet kilkadziesiąt eventów to ich odtworzenie może mieć pomijalny narzut. Zwykle jeśli aplikacja jest oparta standardowo na bazie SQL to też często na jedną encję biznesową leci wiele zapytań z joinami które swój koszt mają.

Czyli jeśli mamy taki read model pod Query to tam mutujemy stan przez UPDATE na naszej stronie READ, tak? No tylko teraz to nie robi nam problemu dwóch źródeł prawdy? Co jeśli pójdzie event do event store, a insert do naszego read modelu się nie powiedzie?

Nie znam się, no ale można próbować ponownie, skasować read model i przebudować od nowa itp.

LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

Rozumiem, a w zasadzie co powinien taki Event zawierać? Id eventu, Id agregatu, timestamp? No, ale w zasadzie powinien też trzymać dane, co się dokładnie zmieniło no i o ile.
No, bo np. mamy coś takiego:

Kopiuj
class User {
 UUID id;
 String login;
 String password;
 String email;
 
 User changePassword(PasswordChanged passwordChanged) {
     this.password = passwordChanged.getPassword();
     eventPublisher.publish(passwordChanged);
     return this;
 }

User changeEmail(EmailChanged emailChanged) {
     this.email = emailChanged.getEmail();
     eventPublisher.publish(emailChanged);
     return this;
 }
}

No i teraz, jak skladamy coś takiego do EventStore to jak jednoznacznie pokazywać gdy pobieramy to z tego eventstora, że dla danego User'a zmieniło się w tym evencie właśnie to pole i to z taką wartością? W dodatku co sprawdza się najlepiej jako EventStore? NoSQL, SQL, Key Value, czy może jakaś Kafka?

Aventus
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
9

Ja zacznę od początku:

W takim razie jeśli Command nam produkuje event to powoduje jednocześnie zapis do bazy Query. No i tutaj na obecnym stanie w bazie Query leci UPDATE czy INSERT, który historyzuje poprzedni stan, a nowy ustawia jako aktualny? Czy może wydzielić to w ogóle na 3 story (Command, Query, Event?). W dodatku ten nowy stan tworzymy na podstawie eventów czy na podstawie obecnego stanu? Przykład: konto bankowe ma 4500 zł, przychodzi event DepositMoney i teraz bierzemy obecny stan i dodajemy do niego value tego eventu (np. 500 zł?)

Jak już wspomniał @mad_penguin, commands to jedynie zasygnalizowanie intencji. Między przetworzeniem command i opublikowaniem eventu należy dokonać walidacji, logikę biznesową itp. Wykonanie command może, a czasem nawet musi się nie powieść. Np. jeśli command to polecenia wybrania z konta sumy pieniędzy której na tym koncie nie ma. Event to z kolei informacja o tym co się stało w systemie, i sam w sobie nie może zostać odrzucony. Przy odtwarzaniu stanu agregatu (jeśli mowa o event sourcing to warto zapoznać się z ideą agregatów) event musi zostać zaakceptowany. Jeśli event wprowadza w błąd stan systemu z punktu widzenia zasad biznesowych, to należy opublikować kolejny event naprawiający ten stan (tzw. compensating event). Co za tym idzie, wszelkie "encje" (a tak naprawdę agregaty, a więc zbiór encji i value objects) powinny być odtwarzane za każdym razem (budowane) za pomocą eventów, ponieważ tylko to załadowanie eventów zapewnia że jest to aktualny stan obiektu. Queries niosą ze sobą eventual consistency, a więc nie ma gwarancji że są aktualne.

Punkty 2 i 3

Tutaj również za bardzo się nie wypowiem co do implementacji technicznej. Ogólnie najważniejsze jest aby każdy event był zawsze zapisywany (transakcyjność).To czy i jak później się go wyśle do kolejek ma drugorzędne znaczenie. Najważniejsze aby każdy event znalazł się w event store- nie ważne czy jest to jakaś dedykowana technologia (np. https://eventstore.com/) czy też własna implementacja. Można np. zastosować tzw. outbox pattern, a więc event zostaje zapisany do event store a następnie jakiś proces w tle emituje ten event do kolejki.

Co do tego kiedy stosuje się brokery- wtedy kiedy eventy (i czasem commands) muszą docierać do modułów poza procesem. Np. kiedy event opublikowany przez agregat A w serwisie A musi zainicjować jakiś proces biznesowy w agregacie B w serwisie B, zakładając że oba serwisy są niezależnymi od siebie procesami i komunikują się sieciowo.

Czyli przy komendzie rozgłaszamy eventy, które zapisujemy do event store, a command nie jest w zasadzie w ogóle persystentne?

Tak

No właśnie, czy takie Query prosto z event store nie jest dość powolne? No, bo tutaj w tym przypadku jeśli nie trzymamy nigdzie stricte stanu naszej encji to musimy zaciągnąć wszystkie eventy i na ich podstawie uzyskiwać obecny stan? Czy może tworzy się do tego wtedy jakieś Cache, które pozwalają nam np. ogarnąć stan naszej encji przy danym evencie w taki sposób żebyśmy nie musieli zaciągać całej historii, a jedynie od danego eventu?

Query to read-model, a więc używane jest np. do zwrócenia danych użytych do wyświetlania po stronie klienta. Query nie powinno być używane po stronie zapisów (commands) ponieważ jak już wspomniałem jest eventually consistent, a więc wykonywanie operacji biznesowych było by z góry narażone na poważne błędy.

Do budowania queries (read models) używa się projekcji, a więc procesów (lub nie procesów, zależnie od technologii) nasłuchujących danych eventów i na podstawie tych eventów budujących read model.Wyobraź sobie że chcesz zbudowań stan konta- do tego będziesz nasłuchiwał każdego eventu związane z kontem. Przy pierwszym evencie (np. AccountOpened/AccountCreated) utworzysz nowy rekord konta w bazie (zakładając że zapisujesz read modele to bazy właśnie) a następnie aktualizował ten rekord przy każdym kolejnym evencie dla tego konkretnego konta.

Jeśli chodzi o ładowanie stanu agregatów z eventów, to oczywiście może być tak że masz tysiące eventów i ładownie ich przy każdej obsłudze command może przynieść problemy wydajnościowe. By temu zapobiec stosuje się snapshots, ale nie należy tego mylić z tym czym są queries. Ponadto przy mniejszej ilości eventów składających się na agregat ładowanie ich za każdym razem nie jest tak naprawdę problemem przy dzisiejszych technologiach. W związku z tym snapshots należy raczej stosować wybiórczo, tam gdzie naprawdę mogą przynieść korzyści.

No i teraz, jak skladamy coś takiego do EventStore to jak jednoznacznie pokazywać gdy pobieramy to z tego eventstora, że dla danego User'a zmieniło się w tym evencie właśnie to pole i to z taką wartością? W dodatku co sprawdza się najlepiej jako EventStore? NoSQL, SQL, Key Value, czy może jakaś Kafka?

Twój model agregatu jest błędny, bo metody które (jak mniemam) mają obsłużyć commands przyjmują eventy. One powinny zamiast tego przyjmować albo parametry albo właśnie obiekt command, np:

Kopiuj
User changeEmail(ChangeEmail changeEmail) { //To jest command
     if (...) { // Sprawdzenie jakiś zasad biznesowych
        // Ewentualny błąd jeśli jakieś wymogi nie są spełnione
     }

     this.email = emailChanged.getEmail();
     eventPublisher.publish(new EmailChanged(...));// Opublikowanie eventu- na tym etapie agregat sygnalizuje że wszystko się zgadza i email został zmieniony
     return this;
 }

Poza tym event powinien oczywiście posiadać powiązane IDs jak i wartości, np. w przypadku powyżej nowy email.

Tutaj kilka linków do artykułów, mam nadzieję że pomocnych:
Implementing an Event Sourced Aggregate
DDD – The aggregate
Event Sourcing (ogólnie)
Event Sourcing: Projections

Tutaj jeszcze link do mojej wypowiedzi na forum na temat czym tak naprawdę są agregaty. Temat dotyczył DDD ale sama idea agregatów dotyczy zarówno DDD jak i event sourcingu.

Obecnie pracuję nad własną implementacją event store w oparciu o bazę NoSQL. Może za jakiś czas wrzucę to na GitHub (obecnie mam w prywatnym repo) co by zaprezentować przykładową implementację jak i używanie event sourcingu z agregatami.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
edytowany 3x, ostatnio: Aventus
Skoq
  • Rejestracja:około 6 lat
  • Ostatnio:około 13 godzin
  • Lokalizacja:Kraków
  • Postów:255
0

Fajnie wyjaśnione ale mam jedno pytanie. Mając coś takiego:

Kopiuj
User changeEmail(ChangeEmail changeEmail) { //To jest command
     if (...) { // Sprawdzenie jakiś zasad biznesowych
        // Ewentualny błąd jeśli jakieś wymogi nie są spełnione
     }

     this.email = emailChanged.getEmail();
     eventPublisher.publish(new EmailChanged(...));// Opublikowanie eventu- na tym etapie agregat sygnalizuje że wszystko się zgadza i email został zmieniony
     return this;
 }

Rozumiem, że publikujemy event tylko jak wszelka walidacja itp. zostanie zakończona sukcesem? Co odnośnie faili? Wtedy nie ma sensu zapisywania eventu w event storze bo nie zmienił się stan obiektu, tak?


I tak to właśnie jest
LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
1

@Skoq: Dokładnie, event zapisujemy tylko wtedy gdy wszystko przejdzie pomyślnie. To jakby skutek pomyślnie użytego Command.

@Aventus @mad_penguin wiele mi to nakreśliło, zaraz zabieram się za te linki które podesłałeś i spróbuję sobie jakiś nawet głupi "sklep" w takim podejściu zakodzić.

edytowany 1x, ostatnio: lavoholic
semicolon
  • Rejestracja:ponad 5 lat
  • Ostatnio:prawie 5 lat
  • Postów:114
0

Taki event sourcing czy cqrs to wycinek znanego i lubianego mvcc tyle, że bez cc :-) Ogólnie w trakcie nauki czy też wyboru jaki się dokonujesz warto też spojrzeć na to co właśnie tracisz.

Docelowo eventy jakie wpadają są zapisywane, ich się nie wycofuje, one są źródłem prawdy, co nie? To podejście oddaje programistom większą kontrolę nad tym co robią, ale w praktyce jest to bardzo uciążliwe wymaganie do spełnienia z poziomu samej aplikacji, która wykonuje też biznesową logikę - to tak jakby zadania w obrębie SQL sprowadzać do jednego spójnego zapytania / insertu.

W społeczności clojure znanym przykłademem event sourcingu / cqrs jest baza datomic - ale to jest to baza obsługująca transakcje zgodne z ACID, robiona przez łebskich ludzi, a nie przypadkowych średniaków. Natomiast bez transakcji taki event sourcing czy cqrs gwarantuje rozjazd w spójności danych. Wtedy mam czyste wątpliwości na ile źródło prawdy jest prawdą :-)

edytowany 4x, ostatnio: semicolon
LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

Mam jeszcze też pytanie co do czystego CQRS bez ES, wtedy to wyglądałoby tak:

Kopiuj
User changeEmail(ChangeEmail changeEmail) { //To jest command
     if (...) { // Sprawdzenie jakiś zasad biznesowych
        // Ewentualny błąd jeśli jakieś wymogi nie są spełnione
     }

     this.email = emailChanged.getEmail();
     //bez wypuszczania eventu
     return this;
 }

Następnie takiego User'a rzucamy do jakiegoś CommandHandlera, który zapisze nam go do repo?

Aventus
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
4

@Skoq:

Rozumiem, że publikujemy event tylko jak wszelka walidacja itp. zostanie zakończona sukcesem? Co odnośnie faili? Wtedy nie ma sensu zapisywania eventu w event storze bo nie zmienił się stan obiektu, tak?

To już zależy od tego jak wygląda Twój system, czy obsługujesz command synchronicznie czy asynchronicznie (np. jakiś worker nasłuchujący na kolejce). W pierwszym przypadku możesz po prostu rzucić wyjątkiem/zwrócić wynik. W tym drugim raczej nie obejdzie się bez eventu z failem (np. ChangingUserEmailFailed chyba że nie masz potrzeby nigdzie zwracać wyniku operacji (co mało prawdopodobne).

@semicolon

ale to jest to baza obsługująca transakcje zgodne z ACID, robiona przez łebskich ludzi, a nie przypadkowych średniaków. Natomiast bez transakcji taki event sourcing czy cqrs gwarantuje rozjazd w spójności danych. Wtedy mam czyste wątpliwości na ile źródło prawdy jest prawdą

Tyle że w przypadku CQRS (a co za tym idzie często i event sourcingu, bo te zagadnienia prawie zawsze idą w parze) to się nazywa właśnie eventual consistency. Zamiast z tym walczyć po prostu przyjmuje się to za naturalny element systemu, tym bardziej że bardzo często nie jest to przeszkodą wbrew temu co mogło by się wydawać na pierwszy rzut oka. To co ważne to transakcyjność po stronie zapisów (Commands), ponieważ w event sourcingu jak sama nazwa wskazuje to właśnie eventy są the only source of truth.

Oczywiście prawdą jest że wybierając takie a nie inne podejście coś zyskujemy, a coś tracimy. Tak jak jest ze wszystkim innym w wyborze architektury/wzorców.

@lavoholic

Następnie takiego User'a rzucamy do jakiegoś CommandHandlera, który zapisze nam go do repo?

Mi ogólnie ciężko wyobrazić sobie "czysty" CQRS bez eventów w innej postaci niż przy wykorzystaniu wzorca mediator i wykonywaniu wszystkich operacji w pamięci (procesie). Ale nawet i przy wykorzystaniu mediatora, nic nie stoi na przeszkodzie abyś nadal publikował eventy- różnica będzie polegała na tym że nie będziesz ich używał do budowania stanu obiektu (encji), a jedynie widoków (queries). To taka forma materialized view, gdzie ten view to właśnie strona z Query z CQRS.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
edytowany 3x, ostatnio: Aventus
Zobacz pozostałe 2 komentarze
Aventus
Poza tym co rozumiesz przez mówienie klientowi? W przypadku event sourcingu najczęściej inaczej również podchodzi się do obsługi danych po stronie widoku- np. przy tworzeniu nowego użytkownika "tworzysz" go i wysyłasz do backendu. Następnie czekasz na wynik operacji, i jeśli jest pozytywny to nie wyciągasz tego użytkownika od razu z backendu skoro już i tak go masz w przeglądarce. Co za tym idzie, zanim będziesz musiał następnym razem wysłać zapytanie do serwera o to aby wysłał Ci (widok) użytkownika, strona Query już zostanie zaktualizowana.
semicolon
Uczę się :D Natomiast o problemach z eventual consistency dowiedziałem się rozpatrując różne bazy. Wcześniej nie było to dla mnie takie oczywiste. Natomiast sam przypadek ES był z pozoru prosty. ES koncentruje uwagę na tym, że teraz nie myślisz o miejscach, które aktualizujesz. Myślisz o wartościach, faktach, które opisują historię - to jest super. Ale przy okazji myślisz, że stan masz spójny dla dowolnego momentu - a tak w praktyce nie jest.
semicolon
Poza tym co rozumiesz przez mówienie klientowi? nic po prostu stwierdzenie Naturalny element systemu brzmi jak ciekawe wytłumaczenie. przy tworzeniu nowego użytkownika "tworzysz" go i wysyłasz do backendu - użytkownika tworzę jednym insertem, moglibyśmy podyskutować o czymś bardziej złożonym. Rozumiem korzyści jakie wynikają z ES, ale musisz wtedy robić większe komendy tak, aby operacja była niepodzielna. Takie założenie już samo w sobie oznacza zmianę technik programowania, pewien narzut, który ponosisz - i nie zamierzam z tym dyskutować. Jest to dla mnie oczywiste.
Aventus
@semicolon: ok rozumiem. Co do tego- użytkownika tworzę jednym insertem, moglibyśmy podyskutować o czymś bardziej złożonym chodziło mi o tworzenie użytkownika z perspektywy aplikacji klienta (np. przeglądarka). I oczywiście specjalnie użyłem banalnego przykładu, w dodatku zarządzanie użytkownikami niekoniecznie jest najlepszym elementem systemu gdzie należy stosować ES.
Aventus
Co do reszty tego co napisałeś to wszystko się zgadza.
LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

Muszę przyznać, że teraz trochę nie czaję. Czym się różnią te dwie sytuacje - budowanie stanu oraz użycie eventów do query.

Aventus
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
4

Czym się różnią te dwie sytuacje - budowanie stanu oraz użycie eventów do query.

Wyobraź sobie że dostajesz command aby zmienić email użytkownika. Na tym etapie ładujesz agregat (User) przy użyciu eventów, ponieważ jak już wcześniej wspomniałem jest to Twój only source of truth. Kiedy użytkownik zostanie zaktualizowany (opublikowany event), obsługujesz ten event w projekcji i aktualizujesz widok (Query side) w bazie danych czy gdziekolwiek to masz. Teraz wyobraź sobie że wchodzisz na stronę zarządzania użytkownikami- wszyscy użytkownicy którzy muszą zostać wyświetleni są ładowani z bazy widoku (Query). To nie jest ten sam obiekt User którego używasz to zapisów (commands). To jest wyspecjalizowany widok użytkownika, który może posiadać dodatkowe atrybuty, np. ostatnie 3 zamówienia online które nie są elementem agregatu User. Co za tym idzie, możesz budować widok użytkownika z eventów pochodzących z wielu różnych źródeł. Kiedy znowu chcesz zmienić email konkretnego użytkownika, wysyłasz do backendu jego ID i nowy email, i znów ładujesz agregat użytkownika za pomocą eventów. I tak dalej...


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
semicolon
  • Rejestracja:ponad 5 lat
  • Ostatnio:prawie 5 lat
  • Postów:114
1

Tu nie chodzi o stan, ani o query - a o potencjalną możliwość jaka wynika z odpytywania o historię.

To co daje Ci ES to tylko fakt, że nie gubisz zmian. Baza PostgreSQL je gubi, ona za każdym razem je gubi, gdy coś nadpisuje.

Po to jest podział, abyś mógł wrócić do dowolnego miejsca w czasie - to jest wartość biznesowa, móc odtwarzać i analizować zdarzenia.

Ludzie tak robią, bo przestrzeń dyskowa teraz jest tania. Natomiast reszta tematów/pojęć, od których rozpoczynasz naukę ES to tylko skutek uboczny.

Eventy same w sobie są nieużyteczne, ich jest za wiele byś mógł w rozsądnym czasie udzielić odpowiedzi na podstawowe pytania, także po to budujesz stan, który w pewnym stopniu pełni analogiczną rolę co indeks w typowej bazie. Łapiesz?

edytowany 3x, ostatnio: semicolon
Aventus
Ja bym jednak zaznaczył że ta "historia" to niekoniecznie jedyny/gówny powód dla stosowania ES. Są systemy które tego nie potrzebują a i tak stosują ES. Inną ważną zaletą stosowania ES jest to, że przy rozproszonych systemach komunikujących się ze sobą za pomocą eventów (event-driven) otrzymujemy wcześniej wymienione "jedyne źródło prawdy". Nie ma ryzyka że po puszczenia eventu na kolejke może przepaść i jakiś serwis tego eventu nie otrzyma, bo w pierwszej kolejności event jest zapisywany do event store. W przeciwnym razie cała operacja się nie powodzi.
semicolon
No jeśli kolejka nie jest durable to taka rzecz jest oczywista, ale wiesz co podciągnałbym to co mówisz do przypadku gier. Myślę (chociaż tego nigdy nie próbowałem), że w przypadku gier sieciowych taka rzecz ma większe wzięcie. Jako, że rozgrywka jest dynamiczna, wiele się dzieje to jednocześnie zejście do poziomu eventów i to must have i faktycznie tu niekoniecznie potrzeba wzglądu w historię gra pierwsze skrzypce.
semicolon
Z innej paki możliwość odtworzenia historii w dowolnym momencie to też dobra rzecz z punktu widzenia testowania, odtwarzania przypadków, które wydarzyły się na produkcji więc sama praktyka, jej idea nie jest zła.
semicolon
A co do analizy z historiią - to często widziałem dyskusje datomic vs postgresql. Zwolennicy postgresqla mówią, że oni sami mogą sobie tak zaprojektować tabelki, by pamiętać wszystkie zmiany. Problem w takim podejściu jest oczywisty, nie zawsze wiadomo kiedy taka rzecz jest potrzebna, a gdy w końcu taka rzecz stanie się ważna, wtedy odwrócenie projektu to nie mały koszt. Także czasem ta analiza nie jest wymogiem, ale to nie wyklucza tego, że się nim stanie. Tutaj ES zostawia furtkę dla takich potrzeb i to też jest spoko.
LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

Już widzę, trudno przestawić głowę z myślenia tabelkowego z RDBMS na myślenie gdzie żyje się bardziej obiektowo niż strukturalnie. Tak samo wydzielanie Bounded Contextow. Gdzie do tej pory User był wszystkim, a teraz jest oddzielnie Payerem, Customerem, Subjectem czy Receiverem. Nie wiem kiedy mi się to uda.. :D Musiałbym chyba być najpierw częścią takiego systemu żeby samemu go jakoś stworzyć, szczególnie z takim expem..

@Aventus: ejszcze mam też pytanie: zakładając, że mamy ten Read Model w postaci jakieś zwykłej bazy relacyjnej. Czyli przy Command zapisuję mój event to Event Store + zapis do Read Modelu, prawda? Wtedy np. mając takie coś:

Kopiuj
class Order {
     UUID orderId;
     List<UUID> items;
     UUID customer;
     Price totalPrice;
     
     Result<OrderPlacingFailed, Order> placeOrder(OrderPlaced orderPlaced) {
          if (validation) {
              return Result.failure(OrderPlacingFailed);
          }
          this.items = orderPlaced.getItems();
          this.customer = orderPlaced.getCustomer();
          return Result.success(this);
     }    
}

Tutaj rozumiem, że Order powinien być moim agregatem, prawda? Więc powinien on posiadać w sobie List<UUID> items czy List<Item> items? Tak abym mógł tutaj obliczyć totalPrice? Czy taki agregat powinien mieć wtedy zależności do mojego Repo od Read Modelu oraz EventPublishera? Czy powinienem mieć jakiś handler, który by to wszystko robił? Natomiast wtedy trochę tracę tą "logikę" w moich encjach i stają one się dość anemiczne.
Ba czy Order nie powinien mieć też w sobie w jakim jest stanie, jaka będzie metoda zapłaty, sposób dostawy?

edytowany 2x, ostatnio: lavoholic
Aventus
Dokładnie, to całkowicie inne podejście i sposób myślenia. Dobrze że to dostrzegasz. Wiele niepowodzeń ze stosowaniem ES brało się właśnie z tego że stosowano ES ale mentalnie pozostając w świecie relacyjnych baz danych.
LA
Właśnie widzę, że to nie jest rzecz na tydzień, ale całkowita zmiana myślenia. Spróbuję stworzyć sobie coś takiego dla funu, trochę pewnie buzzwordowo. Najciekawiej byłoby się wlepić w jakiś projekt open source/najlepiej pracę gdzie takie podejście istnieje - choć troszkę.
Skoq
też chyba czegoś poszukam (opensource) bo temat ciekawy, a sporo wolnego czasu teraz ;p
LA
Jak coś znajdziesz to poinformuj mnie od razu @Skoq :D
Skoq
  • Rejestracja:około 6 lat
  • Ostatnio:około 13 godzin
  • Lokalizacja:Kraków
  • Postów:255
1

Ba czy Order nie powinien mieć też w sobie w jakim jest stanie, jaka będzie metoda zapłaty, sposób dostawy?

Zakładając, że masz wydzieloną domenę tworzenia zamówień to wydaje mi się równie dobrym pomysłem wydzielenie domeny odpowiedzialnej za wysyłkę (np. w zależności czy Twój totalPrice jest większy od X zł to wysyłka jest tańsza/darmowa) i płatności. W takim wypadku Twój dto wychodzący z tej pierwszej domeny nie miałby wiedzy o metodzie zapłaty czy sposobie dostawy. Ale to tylko przemyślenia raczkującego programisty :D


I tak to właśnie jest
edytowany 1x, ostatnio: Skoq
Aventus
Przemyślenia idące w bardzo dobrym kierunku ;)
LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

Racja, czyli w zasadzie totalPrice z Order powinien delegować jakąś komendę do modułu Payment, a następnie Shipment, nie?

Aventus
Tu raczej jakiś element systemu powinien REAGOWAĆ na event wychodzący z Order. Order nie powinien wiedzieć co musi być później zrobione, a tym bardziej command nie powinien być wysyłany z agregatu.
Charles_Ray
Delegowanie komend brzmi słabo, bo couplujesz ze sobą różne domeny.
Aventus
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
3

@lavoholic: agregaty powinny ograniczać się do pewnego określonego zestawu powiązanych operacji. Czasem na agregat należy patrzyć jak na na maszynę stanu opisującą pewien proces. Co do Twojego przykładu, to przede wszystkim znów wygląda on błędnie bo znów przyjmuje event zamiast command. Co gorsza, zwraca konkretny wynik (fail)- a co z sukcesem? Chyba że ja nie do końca rozumiem co tam próbujesz osiągnąć.

Ogólnie zasada wywodząca się z DDD jest taka że agregaty mogą posiadać tylko ID innych agregatów, a nie referencje do obiektów. W przeciwnym razie przekraczamy granice transkcyjności jednego agregatu. Twój przykład zamówień jest dosyć kanonicznym zagadnieniem, i zazwyczaj rozwiązanie przedstawia się tak że masz swój Order który ma kolekcję OrderItem(s). Natomiast każdy OrderItem nie jest prawdziwym agregatem (tym jest właśnie Twój Item) a jedynie value objectem który posiada właśnie ID itemu jak i dodatkowe atrybuty- np. cenę pojedynczego itemu, jego ilość (w zamówieniu, nie ilość w magazynie) itp. No bo np. w przyszłości cena itemu może się zmienić, ale nie zmieni to faktu że to konkretne zamówienie ma order item z inną ceną- starą, albo ze zniżką itp.

Czy taki agregat powinien mieć wtedy zależności do mojego Repo od Read Modelu oraz EventPublishera?

Znów, tu nie powinno być żadnej zależności agregatu od read modelu. Jeśli np. masz wymóg (całkiem słuszny) by sprawdzić czy cena każdego order item zgadza się zanim dodajesz go do agregatu, to wykracza już to poza granicę transakcyjności tego agregatu. I tu znów z pomocą przychodzi DDD, a konkretnie domain services. Jest to więc serwis (handler) który przed wysłaniem command do agregatu sprawdzi pewne zasady biznesowe jak i wyciągnie potrzebne dane. Alternatywą byłoby wstrzykiwanie niektórych zależności bezpośrednio do agregatu, ale tu sam musisz sobie odpowiedzieć co wybrać. Ogólnie odradza się wstrzykiwania zależności do agregatu, szczególnie wstrzykiwania read-modeli, ale ja zawsze twierdzę że zasad nie należy się sztywno trzymać jeśli ich nagięcie ma sens dla rozwiązania konkretnego problemu.

Jak słusznie zauważyłeś może to prowadzić do anemicznych modeli, ale tam gdzie jest sens to stosować należy to robić. Innym przykładem gdzie zastosować serwis domenowy i obsługę operacji która wykracza poza transakcyjność jednego agregatu jest zapewnienie unikalności adresu email przy tworzeniu nowego użytkownika.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

No i kto znowu właśnie widzę jak moje myślenie "relacyjne" gubi, a można to tak właśnie łatwo obskoczyć jak mówisz (zaplanowanie Order), natomiast co do komunikacji między modułami to racja - wystarczy wystawić event na który zareagują inne moduły. Jednak w takim wypadku jeśli mamy Order i powinien on opublikować Event na który zareagują inne moduły (no np. ten Payment) to jednak skądś musimy te informacje na temat tego Paymentu wziąć? Czy ten event je przekaże? Natomiast wtedy chyba wybrnąłby za swoją granicę (?). Bo jednak zapalnikiem do tego wszystkiego jest w zasadzie tworzenie Orderu.

Czym w zasadzie różni się Event od Commandu? Z perspektywy teoretycznej wiem, Command jest zapalnikiem natomiast Event jakby jego pozytywnym skutkiem. Natomiast od strony kodu czym byłaby ta różnica na przykładzie chociażby składania zamówienia?

Co do tego modelu to tam na końcu jest return Result.success(this);

semicolon
  • Rejestracja:ponad 5 lat
  • Ostatnio:prawie 5 lat
  • Postów:114
1

@lavoholic: Kup sobie tą książkę: https://pragprog.com/book/egmicro/practical-microservices

Ona Cię krok po kroku wprowadzi w temat. Ja tej książki nie czytałem poza próbkami więc ciężko powiedzieć czy warto. Natomiast przekaz książki zrozumiałem czytając kod źródłowy. To nie było coś szczególnie pochłaniającego.

Aventus
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
3

Co do tego modelu to tam na końcu jest return Result.success(this);

No właśnie na końcu jest że zwracasz success, a metoda zwraca Result<OrderPlacingFailed, Order>. Tego właśnie nie rozumiem.

Czym w zasadzie różni się Event od Commandu? Z perspektywy teoretycznej wiem, Command jest zapalnikiem natomiast Event jakby jego pozytywnym skutkiem. Natomiast od strony kodu czym byłaby ta różnica na przykładzie chociażby składania zamówienia?

Dodając do tego co pisałem wcześniej- klient (przeglądarka) wysyła polecenie dodania przedmiotu do zamówienia. Backend obsługuje request HTTP i zamienia go w command które jest wysłane dalej do warstwy domenowej. Tutaj command zostaje obsłużony przez agregat (lub najpierw serwis domenowy jak wspomniałem wyżej) i może się powieść lub nie. Powiedzmy że każde zamówienie może mieć maksymalnie 10 rożnych przedmiotów. Przy próbie dodania jedenastego, agregat odrzuci polecenie (command) ponieważ złamana zostanie zasada biznesowa. Może to skutkować eventem lub nie, zależnie od przyjętego podejścia. Jeśli operacja się powiedzie to agregat wyemituje event o dodanym przedmiocie zamówienia (OrderItemAdded).

Jednak w takim wypadku jeśli mamy Order i powinien on opublikować Event na który zareagują inne moduły (no np. ten Payment) to jednak skądś musimy te informacje na temat tego Paymentu wziąć? Czy ten event je przekaże? Natomiast wtedy chyba wybrnąłby za swoją granicę (?). Bo jednak zapalnikiem do tego wszystkiego jest w zasadzie tworzenie Orderu.

Tutaj trzeba odwrócić zależność- event emitowany przez Order nie musi wiedzieć nic na temat Paymentu. To Payment reagując na event emitowany przez Order (ale z jakiego agregatu event pochodzi Payment wiedzieć nie musi ani nie powinien!) odpowiednio zareaguje. Znów, zależnie od przyjętego podejścia event (np. OrderConfirmed) może zawierać TotaPrice lub Payment sam sobie obliczy całą cenę na podstawie wszystkich przedmiotów zamówienia (OrderItems), ich ilości oraz ceny w momencie składania zamówienia. Innymi słowy, proces agregatu Order kończy się na tym kiedy użytkownik zasygnalizuje chęć zakończenia zamówienia, a więc nie będzie więcej dodawanych przedmiotów. Mało tego- Order wcale nie musi być odpowiedzialny za sygnalizowanie takich rzeczy! Od tego możesz mieć w ogóle oddzielny agregat, np. OrderCompletion.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
edytowany 1x, ostatnio: Aventus
LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

Hm, no tak takie rzeczy jak TotalPrice to rozumiem. Natomiast co z taką rzeczą jak PaymentMethod? Takie coś w zasadzie już następuje PO złożeniu zamówienia (czyli momencie kiedy użytkownik już niczego nie dodaje). Czy ogólnie np. składanie zamówienia to będzie jeden request, a wybieranie adresu dostawy oraz metody płatności to będzie już drugie zapytanie? Bo właściwie jeśli byłaby to jedność to trudno byłoby określić jak Payment ma wybrać metodę płatności, a Shipment metodę dostawy, prawda?

Ogólnie dzięki za poświęcony czas, że chce Ci się tak mi to tłumaczyć jak głupkowi.. :D

TS
  • Rejestracja:ponad 5 lat
  • Ostatnio:około 6 godzin
  • Postów:853
0

@Aventus

  1. Czy masz może jakieś dobre przykłady jak powinien wyglądać schemat takich eventów?
  2. W jaki sposób powinienem te eventy zapisywać w bazie danych? Jak rozumiem muszę mieć id aktualnego eventu, id poprzedniego eventu i event, czy coś jeszcze?
  3. W jaki sposób obsługiwać potencjalne duplikaty eventów? Czy powinienem cache'ować ostatnie eventy, żeby nie wykonać ich ponownie?
  4. Czy tylko jedna usługa powinna mieć możliwość edycji event logu i głównego źródła prawdy? W innym przypadku chyba trzeba wprowadzić rozproszone transakcje, czy się mylę?
  5. Czy masz może dobrą literaturę na ten temat oprócz wcześniej przytoczonego "Practical Microservices"?
Aventus
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
3

Hm, no tak takie rzeczy jak TotalPrice to rozumiem. Natomiast co z taką rzeczą jak PaymentMethod? Takie coś w zasadzie już następuje PO złożeniu zamówienia (czyli momencie kiedy użytkownik już niczego nie dodaje). Czy ogólnie np. składanie zamówienia to będzie jeden request, a wybieranie adresu dostawy oraz metody płatności to będzie już drugie zapytanie?

Ah już rozumiem na czym polega Twoje zmieszanie. Tak, jak najbardziej! Wszystko złożone na oddzielne requesty. Tak jak masz problem ze zmianą paradgymatu związanego RDBMS na ES, tak tutaj nadal myślisz w kategoriach jakichś bardziej złożonych CRUDów. Podczas kiedy przy zastosowaniu ES i CQRS bardziej należy stosować rozwiązania typu task-based UI. Tylko że raz jeszcze zaznaczam- wcale nie musi (ale może) być tak że to Order inicjuje dalszy proces poprzez wyemitowanie eventu typu OrderCompleted. Możesz zastosowac odwrotne podejście- w czasie kiedy zamówienie jest aktualizowane (OrderItemAdded, OrderItemRemoved) inner serwisy- np. Payment- buduje swoje dane z tych eventów. Następnie kiedy nadchodzi czas kończenia zamówienia request może być np. wysłany bezpośrednio do Payment. Wtedy to np. agregat Payment może wyemitować event, który będzie przechwycony w serwisie zamówień i "zmapowany" na command, np. CompleteOrder. Wtedy agreat wyemituje event OrderCompleted już po zakończeniu płatności, i zaktualizuje swój stan np. po to by dokładnie to samo zamówienie nie mogło zostać znów zrobione.

Bo właściwie jeśli byłaby to jedność to trudno byłoby określić jak Payment ma wybrać metodę płatności, a Shipment metodę dostawy, prawda?

Dokładnie tak. Tym bardziej że przy zastosowaniu mikroserwisów możesz w ogóle mieć oddzielne serwisy API.

Ogólnie dzięki za poświęcony czas, że chce Ci się tak mi to tłumaczyć jak głupkowi.. :D

Nie ma sprawy, sam kiedyś miałem podobne pytania ;)


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
Aventus
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
4
twoj_stary_pijany napisał(a):

@Aventus

  1. Czy masz może jakieś dobre przykłady jak powinien wyglądać schemat takich eventów?

Nie jestem pewny czy rozumiem o co Ci chodzi. Ogólnie to jestem zwolennikiem "czystych" domenowych eventów, a więc payload eventu powinien mieć tylko wartości domenowe. Np. OrderCreated mógłby wyglądać tak w naszej wyimaginowanej domenie:

Kopiuj
{
  "OrderId": "guid",
  "UserId": "guid",
  "OrderTimeout": "timestamp",
  "MaxOrderItems": "10"
}
  1. W jaki sposób powinienem te eventy zapisywać w bazie danych? Jak rozumiem muszę mieć id aktualnego eventu, id poprzedniego eventu i event, czy coś jeszcze?

Tu znów- jestem zwolennikiem tego aby wszelkie dodatkowe atrybuty należały do metadanych eventu, "doklejane" przez infrastrukturę obsługującą eventy. To co będziesz miał w takich metadanych to już całkowicie od Ciebie zależy. Poprzedni event to tzw. causation ID. Chociaż ważniejsze (można polemizować) niż causation ID jest correlation ID, czyli to samo ID dla konkretnego procesu wiążące eventy razem. Np. od momentu kliknięcia "Complete Order" to zakończenia zamówienia wszystkie eventy będą miały to samo correlation ID. Zakładając że masz tabelę Events, taki rekord może posiadać np. kolumny EventId, EventName, Timestamp, Payload, CorrelationId. Oczywiście jest to tylko przykład. Sam przy mojej obecnej implementacji event store ograniczam metadane do minimum, ale może to ulec zmianie.

  1. W jaki sposób obsługiwać potencjalne duplikaty eventów? Czy powinienem cache'ować ostatnie eventy, żeby nie wykonać ich ponownie?

Nie ma co do tego jednej odpowiedzi. Tu można zdać się np. na brokera (jeśli ma możliwość de-duplikacji) lub właśnie trzymać rekord przetworzononych eventów, Zależnie od natury systemu można czyścić takie dane, np. ID eventów starszych niż X dni jeśli wierzymy że tak starte eventy nie mają szans znów zostać wysłane. Eventualnie należy stosować idempotence przy przetwarzaniu eventów, i po prostu liczyć się z tym że mogą trafić się duplikaty. Z własnego doświadczenia mogę jedynie powiedzieć że łatwiej to brzmi w teorii niż zrobić to dobrze w praktyce.

  1. Czy tylko jedna usługa powinna mieć możliwość edycji event logu i głównego źródła prawdy? W innym przypadku chyba trzeba wprowadzić rozproszone transakcje, czy się mylę?

Co rozumiesz przez edycję event logu? Log eventów jest "append only", czyli jedyne co można zrobić to dodać nowy event. Eventów nie usuwa ani nie edytuje się.

Ogólna zasada jest taka że rozproszone transakcje (w rozumieniu takim jak w bazach danych) to zło. Oczywiście znajdą się wyjątki gdzie inaczej nie da się tego załatwić. Natomiast w ogromnej większości przypadków stosuje się tzw. compensating events. Czyli jeśli zostaje wyemitowany event, ale dalej w procesie następuje coś co "unieważnia" poprzedni event. Np. mamy OrderCompleted a następnie płatność się nie udaje. W takim przypadku Payment emituje PaymentFailed, co dalej kończy się tym że agregat Order emituje OrderCancelled, OrderInvalidated lub coś podobnego. Ponadto często w bardziej złożonych, szczególnie rozproszonych procesach stosuje się sagas/process managers.

  1. Czy masz może dobrą literaturę na ten temat oprócz wcześniej przytoczonego "Practical Microservices"?

Ogólnie to proponuję zapoznać się z DDD, bo idzie mocno w parze z koncepcjami które wprowadza Event Sourcing. Np. takie agregaty ES zapożycza właśnie z DDD, tak samo jak pomysł emitowania eventów z agregatów do sygnalizowania zmian w reszcie systemu. Z książek mogę polecić Vaughn Vernon Implementing Domain-Driven Design. Ta książka ma również bardzo dobry aneks odnośnie event sourcingu, w tym przykłady implementacji własnego event store.

Tutaj świetna prezentacja "Not Just Events: Developing Asynchronous Microservices" odnośnie Twojego pytania co do transakcji.
Reference 4: A CQRS and ES Deep Dive z (darmowej) książki CQRS Journey. Ma to już trochę lat ale fundamentalne koncepcje pozostają bez zmian.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
edytowany 2x, ostatnio: Aventus
Zobacz pozostały 1 komentarz
Aventus
@lavoholic No właśnie tak jak zaproponowałem, czyli czysty event nie jest tym samym co event zapisywany w event store. Czysty event jest tylko elementem tego co jest zapisane w event store- stąd wspomniany przeze mnie payload. Co za tym idzie to co zapisujesz do bazy może nazywać się inaczej, np. EventEnvelope.
LA
Znaczy w zasadzie to nadal nie rozumiem. W sensie jak umożliwić to, że jeden Event będzie w bazie miał fieldy: EventId, OrderId, MaxItemsPlaced, a drugi: EventId, OrderId, PromotionalCodeUsed?
Aventus
Payload eventu jest wtedy po prostu zserializowanym obiektem, może mieć jakikolwiek kształt. Zresztą nawet gdybyś zapisywał czysty event jako dokument to również byś mógł to zrobić. Taka jest właśnie cecha NoSQL.
LA
Nie rozumiem też jednej rzeczy.. Skoro nasze event mają być tym jedynym źródłem prawdy to jak to się ma do naszego read modelu? Skoro nasze eventy to tylko jakby strona commandow, a i tak zapisujemy stan do read modelu zaraz po wyemitowaniu eventu.. Czyli mamy jakiś event store w mongo, a np. Read model w jakimś postgresie/ też mongo, a i tak czytamy z postgresa?
Aventus
To zależy co masz na myśli pisząc "czytamy z...". Czytamy w jakim celu? Obsługi widoku? Zwrócenia wyników użytkownikowi? Wtedy tak. Natomiast w celu wykonania kolejnych operacji biznesowych (obsługi command) ładujemy agregaty, a do tego używamy eventów załadowanych z event store. Proponuję raz jeszcze przeczytać co napisałem w tym poście: https://4programmers.net/Forum/1684468, i spróbować porządnie to sobie wyobrazić i zrozumieć.
LA
  • Rejestracja:ponad 5 lat
  • Ostatnio:9 miesięcy
  • Postów:112
0

Jak to wszystko czytam to powątpiewam w siebie czy jest jakakolwiek szansa, że choć coś zbliżonego do tego jestem w stanie zaimplementować na jakimś ubogim systemie.. :D

Aventus
Najlepiej nowe zagadnienia rozgryzać po kawałku ;) zabierz się za jakiś mały projekcik i w razie wątpliwości pytaj tutaj.
LA
Tak planuję właśnie zabrać się za takie typowe rzeczy pokroju sklepu, można dużo tam wpakować funkcjonalności - trudno, że oklepane, zawsze to jakiś start - i zacząć od jakiegoś jednego modułu np. samego zamówienia bez dostaw/płatności itp. Zawsze to coś, potem tylko znaleźć pracę gdzie tak się pisze to robi i voilà :D Obecnie najgorszym problemem jest przestawienie się z SOAP'ów i CRUD'ów w pracy na takie coś, szczególnie że dopiero zaczynam zabawy z takimi rzeczami architektonicznymi, czasami też zastanawiam się czy to odpowiedni moment gdy ma się niecały rok expa.
LA
I raczej nikt nie zapyta mnie na rozmowie na juniora o implementacje event store @Aventus :D
Skoq
jeśli robisz w outsourcingu to nigdy nie wiesz (ale powinieneś wiedzieć xD) na jakie stanowisko Cię polecą do zewnętrznej firmy :D
LA
to jest właśnie ten minus outsourcingów :D
TS
  • Rejestracja:ponad 5 lat
  • Ostatnio:około 6 godzin
  • Postów:853
0

@Aventus
ad 4. Źle się wyraziłem. Chodziło mi o to kto powinien dodawać rzeczy do event logu? Jak rozumiem można to zaimplementować np. na Kafce, że jakiś konsument nasłuchuje na eventy i je wszystkie zapisuje np. jako Avro/Parquet.

  1. Powiedzmy, że komenda "add_client" dodaje klienta i emituje event "add_client". Mam jeszcze jakiegoś workera, który nasłuchuje na event "add_client" i ten worker przygotowuje klientowi jakąś przestrzeń roboczą i emituje event "create_workspace". Teraz załóżmy, że straciłem swoją bazę danych i chciałbym ją odtworzyć. Wrzucam na kafkę event "add_client" oraz "create_workspace", a "add_client" podczas wykonywania dodatkowo emituje "create_workspace" przez co mój workspace utworzony dwa razy. Czy dobrze rozumiem, że taki event log powinien być przepuszczony jeszcze raz przez system, żeby odtworzyć bazę danych czy powinienem jakoś odsiać te zdarzenia pochodne?

Wielkie dzięki za dzielenie się wiedzą i za materiały.

edytowany 1x, ostatnio: twoj_stary_pijany
Aventus
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
3

@twoj_stary_pijany: nie to żebym się czepił ale odnośnie nazw tych eventów- eventy naprawdę powinny być w czasie przeszłym, commands powinny mieć wydźwięk imperatywny ;) Czyli eventy to ClientAdded, WorkspaceCreated itp.

Co do Twojego pytania- podany przez Ciebie przykład używa Kafki a ja nie mam z tym doświadczenia. Czy wyemitowanie eventu oznacza natychmiastowy zapis go do event store? Bo kiedy piszesz "konsument nasłuchuje" to mi na myśl od razu przychodzi coś co po (krótkim) czasie odbierze i zapisze event. No i już masz problem bo skąd gwarancja że między czasie jakiś agregat zostanie wczytany bez tego eventu? W tym scenariuszu masz eventual consistency na poziomie event store, a to nie wróży nic dobrego. Stąd lepszym podejściem jest bezpośredni zapis do event store, a następnie dopiero emitowanie eventów do kolejek- wymieniony wcześniej przeze mnie outbox pattern.

Tak więc odpowiedź na pytanie

.kto powinien dodawać rzeczy do event logu?

brzmi każdy. Każdy serwis przy emitowaniu eventu zapisuje go bezpośrednio do event store. Oczywiście implementację zapisu można wyabstrahować i dostarczyć w formie jakiejś biblioteki (NuGet, Maven itp).

Jeszcze raz to podkreślę- event store to "jedyne źródło prawdy w systemie", a więc de facto eventy muszą być zapisane transakcyjnie.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
edytowany 2x, ostatnio: Aventus
Aventus
  • Rejestracja:prawie 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
2

@twoj_stary_pijany uświadomiłem sobie że pominąłem bardzo istotny szczegół. Kiedy mowa o ES, eventach i agregatach to również mowa o event streams. Stream to nic innego jak zbiór eventów dla konkretnego agregatu- wtedy znając nazwę streamu (związaną z typem agregatu) oraz ID konkretnego agregatu wyciąga się wszystkie (lub ich część) eventy z takiego streamu. Co za tym idzie, metadane eventu o których pisałem wyżej muszą również zawierać co najmniej ID agregatu lub właśnie ID streamu. Często również stosowaną praktyką jest dopisywanie numeru sekwencyjnego eventu w obrębie danego streamu. Czyli stream dla Order 1 może mieć eventy z sekwencjami 1, 2, 3 a Order 2 eventy z sekwencjami 1, 2, 3, 4, 5.
To jak obsługiwać streamy zależy od użytych technologii i implementacji event store. Ja np. wykorzystuje fakt że baza NoSQL której używam jako event store ma rozbudowany mechanizm identyfikatorów z możliwością dodawania automatycznie generowanych sekwencji do końca takiego identyfikatoru. Co za tym idzie ID każdego eventu w event store zawiera jednocześnie dane streamu:

{nazwa agregatu}/{ID agregatu}/{sekwencja eventu}

np:

Kopiuj
orders/af9b6c33-edab-4035-802b-bfa19af842a0/1
orders/af9b6c33-edab-4035-802b-bfa19af842a0/2
orders/af9b6c33-edab-4035-802b-bfa19af842a0/3
orders/af9b6c33-edab-4035-802b-bfa19af842a0/4

Pozwala to również na stosowanie optimistic concurrency jeśli sekwencje dodaje się "manualnie" w kodzie.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
TS
  • Rejestracja:ponad 5 lat
  • Ostatnio:około 6 godzin
  • Postów:853
0

Zacząłem czytać Vernona bo miałem go od dawna w swojej bibliotece i muszę przyznać, że to chyba najgorsza książka jaką czytałem od czasów lektur szkolnych. Takiego lania wody i tekstów w stylu:

Już w następnym rozdziale dowiemy się czym jest kontekst ograniczony, już za chwilę, za momencik.

to dawno nie widziałem.

edytowany 1x, ostatnio: twoj_stary_pijany
Zobacz pozostałe 9 komentarzy
Aventus
czytelnik, który uważa inaczej powinien się na chwilę powstrzymać z werdyktem Pamiętam takie fragmenty, i w zasadzie nie odbierałem ich tak jak Ty. Takie rzeczy był dobrze wyjaśniane dalej, np. zapadło mi w pamięć porównanie różnego podejścia do tego samego agregatu- rozbicie go na bardzo małe agregaty vs jeden duży agregat vs coś pomiędzy. W ogóle u Vernona bardzo podoba mi się jego rozsądne podejście- nie jest zaślepionym dogmatykiem i mówi wprost że zasady trzeba czasem naginać. Ma nawet poświęcone temu paragrafy w książce, które w pewnym sensie zachęcają do naginania.
Aventus
@jarekr000000: nie do końca rozumiem o co chodzi z tym rakiem. Masz na myśli toksyczne środowisko? To chyba znajdzie się wszędzie, zapewne przymykamy tylko na to oko tam gdzie jest coś co sami lubimy/bardziej znamy. Paradoksalnie dużo jadu wylewa się w dyskusjach gdzie ktoś stosował DDD lub ES zwyczajnie źle, na swój sposób, a następnie niepowiedzeniem obarczył właśnie DDD/ES zamiast swoje błędne podejście do tematu. Tutaj to fajnie widać: https://news.ycombinator.com/item?id=13339972
jarekr000000
Chodzi mi o absolutny przerost kodu nad treścią, okraszony do tego często dziwacznymi framorkami - np. Axon. (Chociaż tu uczciwie przyznam, że axona znam bardzo powierzchownie - doprowadził mnie zbyt szybko do wymiotów). Kłótnie o prawdziwość i poprawność ddd też są fajne, zwłaszcza jak Vernon zacznie pitolić bzdury na twitterze (promowane przez niego vlingo jesr miejscami mocno pokraczne).
Aventus
Chodzi mi o absolutny przerost kodu nad treścią Naprawdę nie chcę się wdawać w polemikę bo po co, ale akurat z tym się nie zgadzam. Z doświadczenia mogę powiedzieć że jest na odwrót- programiści często skupiają się na kodzie i technikaliach zamiast nad problemem (domeną) jaki ten kod ma rozwiązać. Co za tym idzie to właśnie kod DDD jest zazwyczaj bardziej zwięzły, skupiony na zasadach biznesowych a nie na sprawach drugorzędnych. Jednocześnie domyślam się skąd może się brać taki odbiór kodu- z patrzenia na przykłady w książkach, a pamiętać należy że służą one tylko nauce..
Aventus
.. To trochę tak jak z nauką OOP- kotki, pieski i inne klasy Animal to również przerost formy nad treścią, ale tylko dla tego że służy on przekazaniu pewnej wiedzy. Nie oznacza że to samo stosuje się w kodzie profesjonalnym, produkcyjnym. Jeśli o kod sam w sobie chodzi, to kod pisany w oparciu o DDD jest często znacznie bardziej ekspresywny. DDD i ES to duży nakład pracy na początku, ale owocem tego jest łatwość rozwijania systemu dalej.
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)