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) {
if (...) {
}
this.email = emailChanged.getEmail();
eventPublisher.publish(new EmailChanged(...));
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.
AventussemicolonsemicolonPoza tym co rozumiesz przez mówienie klientowi?
nic po prostu stwierdzenieNaturalny 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.Aventusuż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