Agregat a duża ilość encji w kolekcji

0

Kombinuję trochę z DDD (na razie bardziej teoretycznie niż praktycznie) i zastanawiam się nad problemem agregatów w skład których wchodzi kolekcja z dużą liczbą encji.

Weźmy standardowy przykład konto + operacje na nim. Z jednej strony ma to sens aby stworzyć agregat Account bo wraz z operacjami tworzy on całość - nie da się niektórych operacji wykonać przy pewnym stanie konta etc. Z drugiej strony pytanie jak wtedy rozwiązać problem, ogromnej ilości operacji przypisanych do danego konta. Agregaty z tego co widzę nie powinny mieć dostępu do repozytorium, więc w teorii wszystkie operacje powinny być wczytane w trakcie wczytywania agregatu, ale z oczywistych względów nie jest to zbyt efektywne.

Rodzi się więc pytanie w jaki sposób ogrywać takie przypadki.

Widzę tutaj kilka opcji:
a) Przy operacjach wymagających przeglądania listy operacji wymagać wstrzyknięcia repozytorium lub jakiegoś serwisu domenowego - np sygnatura do dodania operacji

public function addOperation(OperationInterface $operation, OperationLoaderPort $repository): void

b) Rezygnacja z agregatu i np. synchornizacja stanu przez Domain Services / Domain Events - np. addOperation() publikuje zdarzenie OperationAddedEvent i na tej podstawie Account się przelicza
c) Chat gpt podpowiada aby w ogóle wstrzyknąć repozytorium operacji przez publiczną metodę typu setOperationRepository()

Moim zdaniem jedynie opcja a jest w miarę sensowna. Z jednej strony unikamy ładowania operacji przy każdej interakcji z agregatem, a z drugiej wszystkie operacja możemy wykonać dość efektywnie - np. jeśli potrzebujemy przeliczenia stanu konta to nie musimy pobierać operacji, jeśli potrzebujemy wyświetlić operacje to możemy dodać stronicowanie itp.

Opcja b jest raczej bez sensu bo wtedy zmieniamy naszą domenę ze względu na ograniczenia techniczne.

Opcja c mi się nie podoba bo wtedy klient kodu musi wiedzieć, że przed użyciem niektórych funkcji musi setować repozytorium, a przy innych nie musi.

Pytanie czy na ten problem są jakieś bardziej eleganckie rozwiązania?

0

Ale co ma wyświetlanie operacji do DDD?
DDD służy do modelowania procesów biznesowych, nie pobierania danych z bazy w celu pokazania ich użytkownikowi.

Opcja b też wydaje się spoko, tylko to jest inna filozofia.

0

A kto powiedział, że twoja encja nie może być agregatem? Możesz mieć dwa lub więcej agregatów. Nie znam szczegółów co jest tą encją, ale popatrz jak to robi Vaugh Vernon uznawany za autorytet DDD tuż obok Erica Evansa. https://github.com/VaughnVernon/IDDD_Samples/tree/master/iddd_collaboration/src/main/java/com/saasovation/collaboration/domain/model/forum

W jego kodzie Forum, Discussion i Post są osobnymi agregatami, mimo, że na poziomie bazy relacyjnej wyglądało by to tak, że Forum (1:N) Discussion (1:N) Post i większość osób zamodelowałaby to właśnie w taki sposób, że Forum jest agregatem, który ma kolekcję Wątków (Dyskusji) gdzie każdy Wątek ma kolekcję Postów. Ten sam problem co u ciebie :) Można by to obejść robiąc Wątek jako drugi agregat (obok Forum), ale znów Wątek może mieć pierdyliard postów.

Mam wrażenie że twój model domenowy silnie jest inspirowany relacjami na poziomie bazy danych i wymaganiami ORM-a, a nie faktycznymi relacjami pomiędzy obiektami biznesowymi.

0

@markone_dev: ten przykład podany przeze mnie jest wymyślony na potrzeby zobrazowania problemu, ale myślę że spokojnie można założyć że konto + operacje mają sens jeśli chodzi o agregat w wielu systemach. W zależności od systemu mogą istnieć invarianty typu: nie mogę wykonać operacji jeśli konto jest zablokowane, albo jeśli operacja przekroczy stan konta itp. Ale nie chciałbym tutaj sprowadzać dyskusji do tego czy dany agregaty ma sens czy nie, tylko załóżmy że faktycznie ten agregaty w przypadku danego systemu ma sens, ale wiąże się z ograniczeniami wydajnościowymi o jakich pisałem - czyli np. mam konto, którego użytkownik wykonał już 10k operacji w historii.
Załóżmy, że mój agregat ma szereg invariantów np:

  • nie mogę przekroczyć limitu konta
  • przy obliczaniu limitu konta muszę brać pod uwagę transakcje nierozliczone
  • w danej chwili mogę mieć maksymalnie 5 nierozliczonych transakcji
  • w przeciągu tygodnia nie mogę przelać więcej niż X do tego samego odbiorcy
  • w danym miesiącu nie mogę zrealizować więcej niż 10 transakcji
  • łączna kwota transakcji w danym dniu nie może przekroczyć 10k PLN
  • itd

Jak w takim przypadku podejść do operacji addOperation() na moim agregacie? Czy wstrzyknięcie jakiegoś interfejsu repo jest w takim przypadku OK (czyli podejście A o którym pisałem)?

@somekind: opcja b ma jeszcze ten minus, że w teorii w DDD domain event powinien być obsługiwany asynchronicznie już po zapisaniu agregatu, który go wywołał. Oczywiście znowu potrafię sobie wyobrazić scenariusz, gdzie operacja jest dodana, a dopiero potem jest zrealizowana lub odrzucona, ale załóżmy, że jednak muszę w tym przypadku iść w agregat i nie mogę sobie pozwolić na eventual consistency.

W między czasie wymysliłem jeszcze opcję d, która w wielu systemach mogłaby mieć sens, tj. coś w stylu snapshotu z EventSourcingu i w agregacie pobieranie tylko operacji po snapshot. Ale załóżmy, że jednak w przypadku tego przypadku snapshot nie ma za bardzo racji bytu z jakichś powodów.

0

Wiesz, kto powiedział, że akurat Account musi być agregatem. Być może w tym przypadku agregatem powinien być obiekt reprezentujący twoją operację czyli w terminologii bankowej nazwalibyśmy go Transaction? Tutaj dokładnie taki przypadek rozważa Sławek Sobótka, gdzie masz Zamówienie a w zasadzie Rezerwacja i Produkt i z analizy wychodzi, że to nie Rezerwacja będzie agregatem jak pewnie 98% osób by założyło, tylko agregatem będzie Pozycja Rezerwacji. A potem tłumaczy na rzeczywistym przykładzie właśnie dlaczego wybór Zamówienia/rezerwacji na agregat jest zły - bo właśnie tak jak w twoim przypadku Zamówienie może mieć setki pozycji i to powoduje dokładnie takie problemy jak te wymienione przez ciebie.

0

@markone_dev: dzięki za polecenie filmu. Bardzo ciekawy i pozwala spojrzeć na niektóre tematy z innej perspektywy. Przykład odwrócenia agregat root jest faktycznie ciekawy.

Niemniej tak jak pisałem zastanawiam się co zrobić w przypadku gdy faktycznie agregatem był by w moim przykładzie Account i miało to biznesowy sens. Co w takiej sytuacji proponuje DDD? Zakładam, że są jakieś rozwiązania tego problemu na stole? Zaznaczę jeszcze raz, że jest to czysto teoretyczny problem więc nie przywiązujmy się za bardzo do samego przykładu Account - można sobie pod do podstawić dowolny inny przypadek, gdzie agregat może potencjalnie mieć sporą kolekcję Encji do obsługi pod spodem.

0

Ciężko rozkminić czego naprawdę oczekujesz. Wydaje mi się że lista operacji (jako już wykonana - tak to rozumiem po pierwszym poście) wraz z kontem nie mogą tworzyć agregatu. Każda operacja to use case na które nałożone są jakieś ograniczenia. Każda operacja zmienia stan agregatu czyli konta - więc każda operacja to snapshot agregatu. Piszesz addOperation tak jak by to miała być implementacja persystencji. Nie za bardzo rozumiem "agregat może potencjalnie mieć sporą kolekcję Encji do obsługi pod spodem". Jeśli to mają być operacje jako funkcje in-out na koncie to jest to reprezentacja stanu agregatu - przecież muszą być zapisane aby odtworzyć stan agregatu. Konkludując - DDD chyba nie rozpatruje przypadku agregatu i jego historii tylko agregat i jego business rules.
Ja bym to zrobił każdą operation jako use case. Use case jako pipeline etapów tworzących transakcje wraz z Constraints. Każdy constraint to np AccountLimit.REACHED itd.
Zresztą każde z ograniczeń nałożonych na operację wymaga sprawdzenia stanu agregatu z przeszłości więc albo ściągasz z eventstore albo ze snapszotów agregatu trigerowanych z event store.
Jeśli chodzi o performance to można zrobić materialized view i nie będzie problemu. Pytanie tylko czy istnieją jakieś operacje których funkcje potrzebowały by na wejściu ogromnej liczby snapszotów. Chyba raczej nie. Wystarczy ostatni snapszot i lista operacji (eventów) po nim. Ale to już szczegóły implementacyjne o których domain nie powinien wiedzieć.

1 użytkowników online, w tym zalogowanych: 0, gości: 1