Jak powinniśmy tworzyć nowe zagregowane korzenie w architekturze cqrs? W tym przykładzie chcę utworzyć nowy zagregowany katalog główny AR2, który będzie zawierał odniesienie do pierwszego AR1.
Tworzę AR2 przy użyciu metody AR1 jako punktu wyjścia. Jak dotąd widzę kilka opcji:
- Wewnątrz metody w AR1
createAr2RootOpt1
mogłemnew AR2()
natychmiast wywołać i zapisać ten obiekt w db za pomocą usługi domeny, która ma dostęp do repozytorium. Mógłbym emitować zdarzenie w pierwszym zbiorczym katalogu głównym np.
SholdCreateAR2Event
a następnie mają bezpaństwową sagę, która reaguje na to i wydaje polecenie,CreateAR2Command
które jest następnie obsługiwane i faktycznie tworzy AR2 i emitujeAR2CreatedEvent
. W przypadku korzystaniaSholdCreateAR2Event
ze źródła zdarzeń nie będzie zachowane w magazynie zdarzeń, ponieważ nie wpływa to na stan pierwszego zagregowanego katalogu głównego. (Czy powinniśmy nadal to zapisywać w sklepie z wydarzeniami?)class AR1{ Integer id; DomainService ds; //OPTION 1 void createAr2RootOpt1(){ AR2 ar2 = new AR2(); ds.saveToRepo(ar2); } //OPTION 2 void createAr2RootOpt2(){ publishEvent(new SholdCreateAR2Event()); //we don't need this event. Shoud it still be preserved in event store? } } class AR2{ Integer id; Integer ar1Id; void handle(CreateAR2Command command){ //init this AR with values and save publishEvent(AR2CreatedEvent()); //used for projections afterwards and saved inside AR2 event store } } class Saga{ void handle(SholdCreateAR2Event ev){ emitCommand(new CreateAR2Command()); } }
Który sposób jest bardziej odpowiedni?
źródło
AR1WasCreated
? Powinno byćAR2WasCreated
? Ponadto, jeśli użyję waszej logiki, wysyłam zdarzenie,AR2WasCreated
zanim zostanie ono faktycznie utworzone? I zapisywanie tego zdarzenia w dzienniku zdarzeń AR1 wydaje się problematyczne, ponieważ tak naprawdę nie potrzebuję tych danych w AR1 (nie modyfikuje niczego w AR1).AR1WasCreated
-> SAGA (ma regułę, jeśli A1 został utworzony, a następnie utwórz A2) ->CreateAR2Command
->AR2WasCreated
.Wzory tworzenia są dziwne .
Udi Dahan ma kilka przydatnych rzeczy do powiedzenia na temat ogólnego problemu: Nie twórz korzeni agregujących . Podstawową kwestią jest to, że agregacja nie wyskakuje znikąd i że istnieje język domeny opisujący ich wygląd, który powinien zostać przechwycony w modelu domeny.
Często zdarza się, że podmiot w twoim modelu domeny, który przetwarza polecenie, nie jest podmiotem modyfikowanym przez transakcję. To nie jest złe; to jest po prostu dziwne (w porównaniu do przypadków, w których prosisz byt sam się zmodyfikował).
Twoje drugie podejście jest również OK. „Zdarzenia, które wywołujemy bez faktycznego zapisywania w bazie danych” są czasami nazywane „zdarzeniami domeny”
Podstawową ideą jest to, że w ramach tej samej transakcji moduł obsługi poleceń wywołuje zdarzenie, które przemieszcza się wzdłuż magistrali do modułu obsługi zdarzeń, który umożliwia utworzenie drugiego agregatu. Być może masz nieco lepszą spójność kodu.
Uwaga: w systemach pochodzących ze zdarzeń zwykle nie używasz zdarzeń w ten sposób.
Uwaga: nazwy zdarzeń są zwykle w czasie przeszłym - ShouldCrateAR2 ma niepoprawną pisownię.
Tak, jeśli rzucasz zdarzenie na szynę synchroniczną, aby uruchomić zdalny kod, nie powinieneś zapisywać tego zdarzenia w księdze rekordów. To tylko szczegół implementacji w tej skali.
Unikaj modyfikowania dwóch różnych strumieni zdarzeń w tej samej transakcji. Jeśli to tworzenie oznacza również zmianę w AR1, wówczas zwykłą odpowiedzią byłoby zmodyfikowanie AR1 w tej transakcji, z asynchronicznym subskrybentem tych zdarzeń, który jest odpowiedzialny za uruchomienie polecenia utworzenia AR2.
Idempotentna obsługa poleceń bardzo tu pomaga.
źródło
AR1.doSmthn(AR2 param)
ponieważ każda utworzona projekcja odczytu nie ma pełnych danych, których potrzebuję (tylko AR2 ma pełne dane).