DDD - optymalizacja operacji na dużych kolekcjach wchodzących w skład agregatu i paginacja

0

Edycja: Byłbym wdzięczny o przeniesienie do forum 'Inżynieria oprogramowania'

Witam serdecznie, moje pytanie będzie się odnosić ogólnie do DDD ale posłużę się przykładem, aby łatwiej pokazać o co mi chodzi.
Załóżmy, że chcemy zaprojektować model forum, w którego skład wchodzi następujący agregat:

Wątek << Aggregate Root >>
Post << Entity >>

Wydaje się logiczne, że post nie ma globalnej tożsamości więc będzie wchodził w skład wątku.
W tym momencie wszystko wygląda w porządku, dopóki wielkość kolekcji postów nie będzie miała dużych rozmiarów.

I tutaj moje pytanie - jak powinna wyglądać poprawna paginacja postów. Nie chcę dodawać do wątku metod typu getPosts(int page), które nie mają nic wspólnego z domeną a tworzenie repozytorium postów przeczy idei agregatu.

Moje następne pytanie - zakładamy, że aplikacja ma możliwość operacji na pojedynczych postach, typu: edycja postu, polubienie postu. W sytuacji gdy kolekcja postów danego wątku jest bardzo duża za każdym razem muszę pobrać ją całą i dopiero wtedy wykonać operację na danym poście. Jak poprawnie zoptymalizować takie operację?

Pozdrawiam.

0

A reguły, że coś ma być na N-tej stronie to skąd masz?

0

A może Wątek jest tylko view modelem zasilanym z Postów, które są agregatami? Można to różnie rozegrać, na pewno nie sugeruj się tym, jak to wyglada na UI.

0
yarel napisał(a):

A reguły, że coś ma być na N-tej stronie to skąd masz?

Posty sortowane są według daty utworzenia, to co ma być na której stronie określa dana implementacja repozytorium, aplikacja tylko prosi o numer strony i jej wielkość

0

Rootem agregatu może być równie dobrze para: identyfikator wątku + numer strony które razem stworzą unikalny klucz podstawowy. W ten sposób unikasz metody getPosts(int page)

0

https://dddcommunity.org/library/vernon_2011/

Te strony to taki średni pomysł. A jak ktoś skasuje post, to należy zmieniać wszystkie klucze?

0
Charles_Ray napisał(a):

A może Wątek jest tylko view modelem zasilanym z Postów, które są agregatami? Można to różnie rozegrać, na pewno nie sugeruj się tym, jak to wyglada na UI.

Tzn, model forum zakłada istnienie czegoś takiego jak wątek, który ma dane utworzenia właściciela itd, więc wydaje mi się, że powinien istnieć jako osobny agregat

TomRZ napisał(a):

Rootem agregatu może być równie dobrze para: identyfikator wątku + numer strony które razem stworzą unikalny klucz podstawowy. W ten sposób unikasz metody getPosts(int page)

Jeśli dobrze zrozumiałem to każda strona będzie miała osobną tożsamość, gdzie w tym wypadku będą istotne informacje o wątku jako całości? Nie wydaje mi się to dobre rozwiązanie ale może źle zrozumiałem

0
anon napisał(a):
yarel napisał(a):

A reguły, że coś ma być na N-tej stronie to skąd masz?

Posty sortowane są według daty utworzenia, to co ma być na której stronie określa dana implementacja repozytorium, aplikacja tylko prosi o numer strony i jej wielkość

A czemu nie sortowanie po długości posta? Niech zgadnę, pewnie masz regułę biznesową, która mówi "porządek taki i nie inny" i jakąś wielkość strony.
Reguły biznesowe nie są elementem Twojego modelu?

Jeśli repo zwraca Ci content dla określonej strony, to co w tym złego? Zwalasz robotę na silnik na którym pracuje repozytorium (żeby nie pisać baza relacyjna ;) ) Co tu optymalizować?

0
yarel napisał(a):
anon napisał(a):
yarel napisał(a):

A reguły, że coś ma być na N-tej stronie to skąd masz?

Posty sortowane są według daty utworzenia, to co ma być na której stronie określa dana implementacja repozytorium, aplikacja tylko prosi o numer strony i jej wielkość

A czemu nie sortowanie po długości posta? Niech zgadnę, pewnie masz regułę biznesową, która mówi "porządek taki i nie inny" i jakąś wielkość strony.
Reguły biznesowe nie są elementem Twojego modelu?

Jeśli repo zwraca Ci content dla określonej strony, to co w tym złego? Zwalasz robotę na silnik na którym pracuje repozytorium (żeby nie pisać baza relacyjna ;) ) Co tu optymalizować?

Moje pytanie dotyczy stricte DDD, napisałem wyżej, że wątek jest korzeniem agregatu przez który odwołuje się do postów, nie wiem do czego nawiązujesz pisząc, że zwalam robotę na repozytorium. Wątek i post stanowią całość, jako że wątek jest korzeniem do jego zawartości odnoszę się tylko za jego pomocą, co za tym idzie posty nie mają osobnego repozytorium - są niewidoczne z poza agregatu.

Niech zgadnę, pewnie masz regułę biznesową, która mówi "porządek taki i nie inny" i jakąś wielkość strony.
Reguły biznesowe nie są elementem Twojego modelu?

Strona i jej wielkość nie są elementami domeny tylko aplikacji, porządek jest i jak wyżej napisałem stanowi część modelu

0

Pisząc o zwalaniu pracy na repozytorium, mam na myśli to, że ta cała praca zostanie wykonana po stronie silnika, na którym jest repozytorium (w domyśle: baza danych). Nie będzie przesyłania całości danych do aplikacji, która miałaby je sobie obrobić. Osobiście nie widzę nic złego w takim "getPage" w modelu, a to dlatego że:

Masz wymagania odnośnie prezentacji albo nie masz wymagań.

  Nie masz wymagań 
	-> nie ma co roztrząsać, paginacje nie są biznesowi potrzebne, a robiąc je generujesz problem, gdzie tu zysk i dla kogo?

  Masz wymagania 
	-> robisz model
		-> Model wspiera wymagania -> klawo
		-> Model nie wspiera wymagań -> czy nie trzeba czegoś zmienić? 

Możesz rozważyć różne modele i sprawdzić "mentalnie" jak będą się zachowywać:
screenshot-20191113161649.png

0
TomRZ napisał(a):

Rootem agregatu może być równie dobrze para: identyfikator wątku + numer strony które razem stworzą unikalny klucz podstawowy. W ten sposób unikasz metody getPosts(int page)

Jeśli dobrze zrozumiałem to każda strona będzie miała osobną tożsamość, gdzie w tym wypadku będą istotne informacje o wątku jako całości? Nie wydaje mi się to dobre rozwiązanie ale może źle zrozumiałem

Możesz stworzyć dwa agregaty - jeden do analizy całościowej, a drugi do stronnicowania.

Obydwa będą miały różny root id / pk - bo ten do stronnicowania będzie złożony z id wątku + numer strony a całościowy tyko id wątku.

8

Na wstępie zaznaczam- nie ma złotych środków i jedynie proponuję jedno z możliwych rozwiązań.

Jak zawsze, należy zachować zdrowy rozsądek i wybrać optymalne rozwiązanie. Zwróć uwagę że napisałeś:

Wydaje się logiczne, że post nie ma globalnej tożsamości więc będzie wchodził w skład wątku.

Agregat to przede wszystkim granica transakcyjności (transactional boundary, nie wiem czy tak to się tłumaczy na język polski). Należy się kierować przede wszystkim tą zasadą kiedy myśli się o modelowaniu agregatów. Również nie ma nic złego w bardzo małych, jedno-encyjnych agregatach jeśli ma to sens i spełnia warunek pierwszej zasady. Piszę to dla tego że należało by odpowiedzieć sobie na pewne pytania:

  • Czy zmiana treści postu wymaga zaangażowania wątku?
  • Czy zmiana treści wątku (np. tytuł) wymaga zaangażowania postów?
  • Czy posty mogą zostać usunięte oddzielnie od usuwanego właśnie wątku?

Dodatkowo dochodzi wymieniony przez Ciebie problem potencjalnie dużej ilości postów w wątku, oraz samych postów mających dodatkowe atrybuty (nie mylić z atrybutami w kodzie) takie jak polubienia. Wracamy więc do pytań:

  • Czy załadowanie wątku oznacza konieczność załadowania wszystkich postów wraz z ich "wewnętrznymi" atrybutami?
  • Czy polubienie postu jest elementem "transakcji" całego wątku?

Moim zdaniem na podstawie tych pytań i informacji które podałeś wynika że Post jest świetnym kandydatem aby być agregatem samym w sobie, z encją Post jako root oraz np. Polubienie jako value object zawierający ID Posta i użytkownika który to polubił.

@Charles_Ray wspomniał że Wątek może być tylko view modelem Poszedł w dobrym kierunku, problem tylko że- jak mniemam- wątek również może być edytowany (tytuł, treść?) oraz zamykany i/lub usuwany. W tej sytuacji Wątek to również kandydat na agregat. Natomiast wspomnienie modelu widoku jest jak najbardziej trafne. Należy pamiętać że agregaty należą do warstwy modelu domeny, i same w sobie nie powinny mieć nic wspólnego z obsługą widoku. Co za tym idzie zainteresuj się CQRS i Materialized View. Wydaje mi się że Twój scenariusz wymaga takiego zastosowania- na podstawie tego co zachodzi w domenie będziesz mógł budować widok obsługujący konkretne wymagania- w Twoim przypadku zwracający wątek (a konkretnie informacje o wątku) oraz podzbiór postów filtrowany na podstawie strony którą aktualnie przegląda użytkownik.

1

Matko przenajświętsza, przecież Post i Wątek to dwa agregaty.

A jak będziesz chciał moderować treść postu, to potrzebujesz do tego Wątek?

0

@Aventus
Bardzo dziękuje za merytoryczną odpowiedź, od niedawna zajmuję się DDD i kilka napisanych przez Ciebie linijek
zmieniło moje postrzeganie tego tematu.

Co do CQRS to celowo o nim nie wspomniałem dlatego, że to rozwiązanie wydaje mi się zbyt drastyczne w przypadku
wydaje się prostego problemu jak paginacja. Wyobraźmy sobie, że mamy już stworzony dosyć rozwinięty model
domeny i nagle ze względów technicznych zachodzi potrzeba stronicowania jakiejś kolekcji, która wchodzi w skład agregatu.
Wprowadzenie CQRS z wyżej wymienionego powodu wydaje mi się zbyt drastyczne. Spotkałem się z jednak
z zapożyczoną z CQRS ideą Finder czyli klasy znajdującej się w warstwie aplikacji, która posiada wiedzę o wewnętrznej
strukturze agregatu i pozwala na odpytywanie o jego wewnętrzne elementy, nie jestem jednak pewien czy
nie przeczy to idei agregatu.

@Charles_Ray
Tak jak napisałem wyżej wydaje mi się, że w ten sposób zbliżamy się w stronę CQRS

@yarel
Dziękuję za przygotowany diagram, co do paginacji w warstwie domeny to może podałem zły przykład,
ponieważ możemy się tutaj na siłę doszukiwać znaczenia paginacji w dziedzinie jednak chodziło
mi o sytuację w której paginacja jest elementem wyłącznie technicznym, zwiększającym wydajność, elementem ui.

@WyznawcaDDD
Tak jak wspomniałem wyżej, nie chodzi mi konkretnie o problem forum ale o sytuacje w której musimy stronicować kolekcję która wchodzi w skład agregatu.
Szczerze mówiąc DDD zajmuję się od niedawna więc sytuacja jest czysto teoretyczna, nie wiem czy kiedykolwiek zajdzie taka potrzeba.

2

Co do CQRS to celowo o nim nie wspomniałem dlatego, że to rozwiązanie wydaje mi się zbyt drastyczne w przypadku
wydaje się prostego problemu jak paginacja. Wyobraźmy sobie, że mamy już stworzony dosyć rozwinięty model
domeny i nagle ze względów technicznych zachodzi potrzeba stronicowania jakiejś kolekcji, która wchodzi w skład agregatu.
Wprowadzenie CQRS z wyżej wymienionego powodu wydaje mi się zbyt drastyczne.

Myślę że tutaj mylne są dwie kwestie. Po pierwsze w warstwie domeny chcesz osiągnąć coś co do domeny nie należy- paginacja na potrzeby widoku. Raz jeszcze zaznaczam że agregat to przede wszystkim granica transakcyjności. Paginowanie postów dla widoku nie leży w gestii tej transakcyjności.

Druga sprawa to że jak wiele osób zapewne mylisz podstawy CQRS z tym jak używa się go na poziomie infrastruktury- mając oddzielne bazy danych a czasem nawet serwisy. CQRS to natomiast coś znacznie prostszego, i fundamentalnie oznacza podział pomiędzy klasami odpowiadającymi za modyfikacje stanu aplikacji (polecenia- commands) oraz za odczyt tego stanu (zapytania- queries). Jeśli jest to więc odpowiednie rozwiązanie w Twoim przypadku, nic nie stoi na przeszkodzie aby korzystać z tej samej bazy danych. Różnica w tym że zamiast używać agregatu do załadowania widoku, skorzystasz z oddzielnej klasy reprezentującej widok która np. zostanie zbudowana na podstawie zapytania SQL stworzonego specjalnie na potrzeby tego widoku.

3

Jak @Aventus dobrze prawi: model układasz sobie w oparciu o reguły biznesowe (które musza być spójne natychmiast), które sprawdzasz przy zapisie, natomiast paginacja, grupowanie danych to kwestia odczytu danych, które mogą operować na innym modelu (zoptymalizowanym pod odczyty właśnie). Najważniejsza lekcja moim zdaniem - nie myśl o modelu w kategoriach widoków.

0

@Aventus @Charles_Ray
Rozumiem i jak najbardziej ma to sens, ale jeszcze dopytam, czy warstwy zapisu i odczytu CQRS w kontekście DDD
to odpowiednio warstwa domeny i dodatkowa warstwa odczytu? Czy flow w tym przypadku mógłby wyglądać następująco:

  • UI - Przekazuje Command lub Query do CommandService lub QueryService warstwy aplikacji
  • Application - CommandService lub QueryService zarząda elementami Command Model (Domain) lub Query Model
  • Query Model - Zawiera modele widoku (np. projekcje agregatów) oraz interfejsy repozytoriów potrzebne do ich odczytu
  • Command Model (Domain) - to co w DDD
  • Infrastructure - to co w DDD
1

Jak dla mnie to wygląda OK, poza ostatnim- nie za bardzo rozumiem co infrastruktura ma do DDD. Infrastruktura to infrastruktura dostarczająca implementacji dla abstrakcji wyżej.

1

@Aventus
Tak i o to właśnie mi chodziło. Jeszcze raz dziękuje za pomoc, bardzo mi to pomogło

1

To ja tu tylko zostawię jako taki mały suplement: https://pl.wikipedia.org/wiki/Kulty_cargo

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