Jak utworzyć nowy zagregowany katalog główny w CQRS?

10

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:

  1. Wewnątrz metody w AR1 createAr2RootOpt1mogłem new AR2()natychmiast wywołać i zapisać ten obiekt w db za pomocą usługi domeny, która ma dostęp do repozytorium.
  2. Mógłbym emitować zdarzenie w pierwszym zbiorczym katalogu głównym np. SholdCreateAR2Eventa następnie mają bezpaństwową sagę, która reaguje na to i wydaje polecenie, CreateAR2Commandktóre jest następnie obsługiwane i faktycznie tworzy AR2 i emituje AR2CreatedEvent. W przypadku korzystania SholdCreateAR2Eventze ź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?

Bojan Vukasovic
źródło

Odpowiedzi:

2

Myślę, że ta opcja nie. 2 jest rozwiązaniem z niewielką, ale ważną modyfikacją: AR1nie powinien emitować zdarzenia, którego celem jest utworzenie zdarzenia AR2, lecz powinien emitować AR1WasCreatedzdarzenie. To wydarzenie powinno zostać utrwalone w sklepie z wydarzeniami, ponieważ jest to ważne wydarzenie z okazji narodzin AR1. Następnie SagaWhould listent za AR1WasCreatedzdarzenia i generuje polecenie, aby utworzyć AR2: CreateAR2Command.

Opcja nr 1 jest bardzo błędna. Nigdy nie należy wstrzykiwać tego rodzaju usług domenowych do Aggregate. Aggregatespowinien być czysty, bez żadnych skutków ubocznych innych niż generowanie zdarzeń.

PS Nigdy nie wysyłam zdarzeń od konstruktora, Aggregateponieważ istnieje różnica między tworzeniem instancji obiektu (w sensie języka programowania) a tworzeniem (narodzinami, jeśli chcesz) Aggregate. Emituję zdarzenia tylko z handlemetody (podczas obsługi a command).

Constantin Galbenu
źródło
Co masz na myśli AR1WasCreated? Powinno być AR2WasCreated? Ponadto, jeśli użyję waszej logiki, wysyłam zdarzenie, AR2WasCreatedzanim 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).
Bojan Vukasovic
OK, 3 lata później. Idzie AR1WasCreated-> SAGA (ma regułę, jeśli A1 został utworzony, a następnie utwórz A2) -> CreateAR2Command-> AR2WasCreated.
Bojan Vukasovic
@BojanVukasovic Cieszę się, że zadziałało tak, jak napisałem :)
Constantin Galbenu
2

Jak powinniśmy tworzyć nowe zagregowane korzenie w architekturze cqrs?

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.

W przypadku korzystania ze źródła zdarzeń ShouldCreateAR2Event nie zostanie zachowany w magazynie zdarzeń, ponieważ nie wpływa to na stan pierwszego zagregowanego katalogu głównego.

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.

A może powinniśmy zapisać to w sklepie z wydarzeniami?

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.

VoiceOfUnreason
źródło
Dziękuję za odpowiedź. Jeszcze jedna rzecz, która nie jest w 100% jasna - później, jeśli muszę użyć AR2 jako argumentu do AR1, jak mam to przekazać - ponieważ CQRS stwierdza, że ​​AR powinien być używany tylko do pisania, a nie do wysyłania zapytań. Ale nie mam innej opcji niż użycie, AR1.doSmthn(AR2 param)ponieważ każda utworzona projekcja odczytu nie ma pełnych danych, których potrzebuję (tylko AR2 ma pełne dane).
Bojan Vukasovic
> „Tak, jeśli rzucasz zdarzenie na magistralę synchroniczną, aby uruchomić zdalny kod, nie powinieneś zapisywać tego zdarzenia w księdze rekordów.” Myślę, że zapisanie go ma prawdziwą wartość, ponieważ wiesz, że proces został zainicjowany, aby coś się wydarzyło, teraz możesz także śledzić, czy faktycznie to się zakończyło. Ale myślę, że to zależy od przypadku użycia
Chaosekie