Czy agregaty DDD są naprawdę dobrym pomysłem w aplikacji internetowej?

40

Zajmuję się projektowaniem opartym na domenie i niektóre koncepcje, które napotykam, mają sens z pozoru, ale kiedy myślę o nich bardziej, muszę się zastanawiać, czy to naprawdę dobry pomysł.

Na przykład koncepcja agregatów ma sens. Tworzysz małe domeny własności, abyś nie musiał zajmować się całym modelem domeny.

Kiedy jednak myślę o tym w kontekście aplikacji internetowej, często odwiedzamy bazę danych, aby wycofać małe podzbiory danych. Na przykład strona może zawierać tylko liczbę zamówień wraz z linkami do kliknięcia, aby otworzyć zamówienie i zobaczyć jego identyfikatory.

Jeśli jestem zrozumienia Kruszywa rację, ja zazwyczaj korzystają z repozytorium wzór do zwracania OrderAggregate które zawierają członków GetAll, GetByID, Delete, i Save. Ok, to brzmi dobrze. Ale...

Jeśli zadzwonię do GetAll, aby wyświetlić listę wszystkich moich zamówień, wydaje mi się, że ten wzorzec wymagałby zwrócenia całej listy zagregowanych informacji, kompletnych zamówień, linii zamówień itp. Gdy potrzebuję tylko niewielkiego podzbioru tych informacji (tylko informacje w nagłówku).

Czy coś brakuje? Czy jest jakiś poziom optymalizacji, którego byś użył? Nie wyobrażam sobie, aby ktokolwiek opowiadałby się za zwracaniem całych informacji, gdy nie są one potrzebne.

Z pewnością można tworzyć takie metody w twoim repozytorium GetOrderHeaders, ale wydaje się, że to pokonuje cel polegający na użyciu wzorca takiego jak repozytorium.

Czy ktoś może mi to wyjaśnić?

EDYTOWAĆ:

Po wielu dalszych badaniach wydaje mi się, że rozłączenie polega na tym, że czysty wzorzec repozytorium różni się od tego, co większość ludzi uważa za repozytorium.

Fowler definiuje repozytorium jako magazyn danych, który korzysta z semantyki kolekcji i ogólnie jest przechowywany w pamięci. Oznacza to utworzenie całego wykresu obiektowego.

Evans zmienia repozytorium, aby uwzględnić korzenie agregatów, a zatem repozytorium jest amputowane, aby obsługiwać tylko obiekty w agregacji.

Wydaje się, że większość ludzi myśli o repozytoriach jako o uwielbianych obiektach dostępu do danych, w których po prostu tworzysz metody uzyskiwania dowolnych danych. Nie wydaje się, aby taka była intencja, jak opisano w wzorcach architektury aplikacji korporacyjnych Fowlera.

Jeszcze inni uważają repozytorium za prostą abstrakcję używaną przede wszystkim w celu ułatwienia testowania i wyśmiewania się lub w celu oddzielenia trwałości od reszty systemu.

Wydaje mi się, że odpowiedź jest taka, że ​​jest to znacznie bardziej złożona koncepcja, niż początkowo myślałem.

Erik Funkenbusch
źródło
4
„Wydaje mi się, że odpowiedź jest taka, że ​​jest to o wiele bardziej złożona koncepcja, niż się spodziewałem”. To jest bardzo prawda.
quentin-starin
w tej sytuacji możesz utworzyć serwer proxy dla zagregowanego obiektu głównego, który selektywnie pobiera i buforuje dane tylko na żądanie
Steven A. Lowe
Moją sugestią jest zaimplementowanie opóźnionego obciążenia w asocjacjach głównego agregatu. Możesz więc pobrać listę korzeni bez ładowania zbyt wielu obiektów.
Juliano
4
prawie 6 lat później wciąż dobre pytanie. Po przeczytaniu rozdziału z czerwonej książki powiedziałbym: nie rób zbyt dużych agregatów. Kuszące jest wybranie jakiejś koncepcji najwyższego poziomu ze swojej domeny i zadeklarowanie, że jest to Root to Rule Them All, ale DDD opowiada się za mniejszymi agregacjami. I łagodzi nieefektywności takie jak te opisane powyżej.
kpt. Senkfuss,
Twoje agregaty powinny być jak najmniejsze, a jednocześnie być naturalne i skuteczne w domenie (wyzwanie!). Co więcej, jest całkowicie w porządku i pożądane jest, aby repozytoria miały wysoce specyficzne metody.
Timo

Odpowiedzi:

30

Nie używaj modelu domeny ani agregatów do tworzenia zapytań.

W rzeczywistości to, co zadajesz, jest dość powszechnym pytaniem, że ustanowiono zestaw zasad i wzorców, aby tego uniknąć. Nazywa się to CQRS .

Quentin-Starin
źródło
2
@Mystere Man: Nie, służy do dostarczenia minimalnych potrzebnych danych. To jeden z głównych celów oddzielnego modelu odczytu. Pomaga również z góry rozwiązać niektóre problemy dotyczące współbieżności. CQRS ma kilka zalet po zastosowaniu do DDD.
quentin-starin
2
@Mystere: Jestem dość zaskoczony, że przegapisz to, jeśli przeczytasz artykuł, do którego linkowałem. Zobacz sekcję zatytułowaną „Zapytania (raportowanie)”: „Gdy aplikacja żąda danych ... należy to zrobić w jednym wywołaniu warstwy zapytania, aw zamian otrzyma jedno DTO zawierające wszystkie potrzebne dane. .. Powodem tego jest to, że dane są zwykle sprawdzane wielokrotnie więcej niż zachowanie domeny, więc poprzez optymalizację poprawisz postrzeganą wydajność systemu. ”
quentin-starin
4
Dlatego nie używalibyśmy Repozytorium do odczytu danych w systemie CQRS. Napiszilibyśmy prostą klasę do enkapsulacji zapytania (przy użyciu dowolnej technologii, która była dogodna lub potrzebna, często prosto ADO.Net lub Linq2Sql lub SubSonic pasuje tutaj dobrze), zwracając tylko (wszystkie) dane potrzebne do danego zadania, aby uniknąć przeciąganie danych przez wszystkie normalne warstwy repozytorium DDD. Użylibyśmy repozytorium do odzyskania agregatu tylko wtedy, gdybyśmy chcieli wysłać polecenie do domeny.
quentin-starin
9
„Nie wyobrażam sobie, aby ktokolwiek opowiadałby się za zwracaniem całych informacji, gdy ich nie potrzebujesz”. Próbuję powiedzieć, że dokładnie zgadzasz się z tym stwierdzeniem. Nie pobieraj całej agregacji informacji, gdy jej nie potrzebujesz. To jest rdzeń CQRS zastosowany do DDD. Nie potrzebujesz agregatu do zapytania. Uzyskaj dane za pomocą innego mechanizmu, a następnie rób to konsekwentnie.
quentin-starin
2
@qes Rzeczywiście najlepszym rozwiązaniem jest nie używanie DDD do zapytań (czytaj) :) Ale nadal używasz DDD w części Command, tj. do przechowywania lub aktualizacji danych. Mam do ciebie pytanie, czy zawsze używasz repozytoriów z jednostkami, gdy potrzebujesz zaktualizować dane w bazie danych? Powiedzmy, że musisz zmienić tylko jedną małą wartość w kolumnie (przełącznik jakiegoś rodzaju), czy nadal ładujesz całą jednostkę w warstwie aplikacji, zmieniasz jedną wartość (właściwość), a następnie zapisujesz całą jednostkę z powrotem w DB? Trochę przesady?
Andrew
8

Walczyłem i wciąż mam problemy z tym, jak najlepiej wykorzystać wzorzec repozytorium w projekcie opartym na domenie. Po użyciu go teraz po raz pierwszy wymyśliłem następujące praktyki:

  1. Repozytorium powinno być proste; odpowiada tylko za przechowywanie obiektów domeny i ich odzyskiwanie. Cała inna logika powinna znajdować się w innych obiektach, takich jak fabryki i usługi domenowe.

  2. Repozytorium zachowuje się jak kolekcja, tak jakby była kolekcją zagregowanych korzeni w pamięci.

  3. Repozytorium nie jest ogólnym DAO, każde repozytorium ma swój unikalny i wąski interfejs. Repozytorium często ma określone metody wyszukiwania, które pozwalają przeszukiwać kolekcję pod względem domeny (na przykład: daj mi wszystkie otwarte zamówienia dla użytkownika X). Samo repozytorium można zaimplementować za pomocą ogólnego DAO.

  4. W idealnym przypadku metody wyszukiwania zwrócą tylko zagregowane korzenie. Jeśli jest to nieefektywne, może również zwracać obiekty tylko do odczytu, niż zawierać dokładnie to, czego potrzebujesz (chociaż jest to plus, jeśli te obiekty wartości można również wyrazić w kategoriach domeny). W ostateczności repozytorium może być również używane do zwracania podzbiorów lub kolekcji podzbiorów zagregowanego katalogu głównego.

  5. Takie wybory zależą od zastosowanych technologii, ponieważ musisz znaleźć sposób, aby najskuteczniej wyrazić swój model domeny przy użyciu zastosowanych technologii.

Kdeveloper
źródło
To zdecydowanie złożony temat. Trudno jest przekształcić teorię w praktykę, zwłaszcza gdy łączy ona dwie odrębne i odrębne teorie w jedną praktykę.
Sebastian Patten,
6

Nie sądzę, żeby twoja metoda GetOrderHeaders w ogóle pokonywała cel repozytorium.

DDD zajmuje się (między innymi) zapewnieniem, że dostaniesz to, czego potrzebujesz, za pomocą zagregowanego katalogu głównego (na przykład nie będziesz mieć OrderDetailsRepository), ale nie ogranicza Cię w sposób, o którym wspominasz.

Jeśli OrderHeader jest pojęciem domeny, powinieneś go zdefiniować jako taki i mieć odpowiednie metody repozytorium do ich odzyskania. Po prostu upewnij się, że przechodzisz przez prawidłowy zagregowany katalog główny.

Eric King
źródło
Być może mam tu mylące pojęcia, ale rozumiem wzorzec repozytorium, aby oddzielić trwałość od domeny, używając standardowego interfejsu do trwałości. Jeśli musisz dodać niestandardowe metody dla konkretnej funkcji, wydaje się, że ponownie łączy to wszystko.
Erik Funkenbusch
1
Mechanizm trwałości jest oddzielony od domeny, ale nie to, co jest utrwalane. Jeśli zauważysz, że mówisz na przykład „musimy tutaj wymienić nagłówki zamówień”, musisz modelować OrderHeader w swojej domenie i zapewnić sposób na ich odzyskanie z repozytorium.
Eric King
Nie odkładaj też słuchawki na „standardowym interfejsie trwałości”. Nie ma czegoś takiego jak ogólny wzorzec repozytorium, który wystarczy dla wszystkich możliwych aplikacji. Każda aplikacja będzie miała wiele metod repozytorium wykraczających poza standardowe „GetById”, „Zapisz” itp. Te metody są punktem początkowym, a nie końcowym.
Eric King
4

Moje użycie DDD może nie być uważane za „czyste” DDD, ale dostosowałem następujące strategie w świecie rzeczywistym, używając DDD do magazynu danych DB.

  • Zagregowany katalog główny ma powiązane repozytorium
  • Powiązane repozytorium jest używane tylko przez ten zagregowany katalog główny (nie jest publicznie dostępny)
  • Repozytorium może zawierać wywołania zapytań (np. GetAllActiveOrders, GetOrderItemsForOrder)
  • Usługa ujawnia publiczny podzbiór repozytorium i inne operacje niekrytyczne (np. Transfer pieniędzy z jednego konta bankowego na drugie, LoadById, Search / Find, CreateEntity itp.).
  • Korzystam z Root -> Service -> Repository stack. Usługa DDD powinna być używana tylko do wszystkiego, co Jednostka nie może sama odpowiedzieć (np. LoadById, TransferMoneyFromAccountToAccount), ale w prawdziwym świecie mam tendencję do pozostawania w innych usługach związanych z CRUD (Zapisz, Usuń, Zapytanie), nawet jeśli root powinien być w stanie „odpowiedzieć / wykonać” je sami. Pamiętaj, że nie ma nic złego w zapewnieniu podmiotowi dostępu do innej zagregowanej usługi root! Pamiętaj jednak, że nie uwzględniono by w usłudze (GetOrderItemsForOrder), ale uwzględniono by to w repozytorium, aby umożliwić korzystanie z niego z katalogu głównego agregacji. Pamiętaj, że usługa nie powinna ujawniać żadnych otwartych zapytań, takich jak repozytorium.
  • Zwykle definiuję Repozytorium abstrakcyjnie w modelu domeny (poprzez interfejs) i zapewniam osobną konkretną implementację. W pełni definiuję usługę w modelu domeny wstrzykując do konkretnego repozytorium na jej użytek.

** Nie musisz przywracać całego agregatu. Jeśli jednak chcesz uzyskać więcej, musisz zapytać użytkownika root, a nie jakiejkolwiek innej usługi lub repozytorium. Jest to leniwe ładowanie i może być wykonane ręcznie przy leniwym ładowaniu ubogiego człowieka (wstrzykiwanie odpowiedniego repozytorium / usługi do katalogu głównego) lub przy użyciu i ORM, który to obsługuje.

W twoim przykładzie prawdopodobnie podałbym wywołanie repozytorium, które przyniosło tylko nagłówki zamówień, gdybym chciał załadować szczegóły do ​​osobnego wywołania. Zauważ, że posiadając „OrderHeader”, w rzeczywistości wprowadzamy dodatkową koncepcję do domeny.

Mike Rowley
źródło
3

Twój model domeny zawiera logikę biznesową w najczystszej postaci. Wszystkie relacje i operacje wspierające operacje biznesowe. To, czego brakuje ci w mapie koncepcyjnej, to idea warstwy usługi aplikacji, która warstwa usługi otacza model domeny i zapewnia uproszczony widok domeny biznesowej (jeśli jest to możliwe), który pozwala na zmianę modelu domeny w razie potrzeby bez bezpośredniego wpływu na aplikacje korzystające z warstwy usług.

Idąc dalej. Ideą agregatu jest to, że istnieje jeden obiekt, główny agregat, odpowiedzialny za utrzymanie spójności agregatu. W twoim przykładzie zamówienie będzie odpowiedzialne za manipulowanie jego wierszami zamówienia.

Na przykład warstwa usług ujawniłaby operację taką jak GetOrdersForCustomer, która zwróciłaby tylko to, co jest potrzebne do wyświetlenia listy podsumowań zamówień (jak je nazywasz OrderHeaders).

Wreszcie wzorzec repozytorium nie jest TYLKO kolekcją, ale umożliwia także deklaratywne zapytania. W języku C # możesz użyć LINQ jako Obiektu Zapytania , lub większość innych O / RM również podaje specyfikację Obiektu Zapytania.

Repozytorium pośredniczy między domeną a warstwami odwzorowania danych, działając jak zbiór obiektów domeny w pamięci. Obiekty klienta konstruują deklaracje specyfikacji deklaratywnie i przesyłają je do repozytorium w celu uzyskania satysfakcji. (ze strony repozytorium Fowlera )

Widząc, że możesz tworzyć zapytania na podstawie repozytorium, sensowne jest również zapewnienie wygodnych metod obsługi typowych zapytań. To znaczy, jeśli chcesz tylko nagłówki swojego zamówienia, możesz utworzyć zapytanie, które zwraca tylko nagłówek i udostępnić go z wygodnej metody w swoich repozytoriach.

Mam nadzieję, że pomoże to wyjaśnić.

Michael Brown
źródło
0

Wiem, że to stare pytanie, ale wydaje się, że doszedłem do innej odpowiedzi.

Kiedy tworzę repozytorium, zwykle owija niektóre buforowane zapytania.

Fowler definiuje repozytorium jako magazyn danych, który korzysta z semantyki kolekcji i ogólnie jest przechowywany w pamięci. Oznacza to utworzenie całego wykresu obiektowego.

Trzymaj te repozytoria w swoich serwerach. Nie tylko przechodzą przez obiekty do bazy danych!

Jeśli korzystam z aplikacji internetowej ze stroną z listą zamówień, którą możesz kliknąć, aby zobaczyć szczegóły, są szanse, że chcę, aby moja strona z listą zamówień zawierała szczegółowe informacje na temat zamówień (identyfikator, nazwa, kwota, data) aby pomóc użytkownikowi zdecydować, na który chce spojrzeć.

W tym momencie masz dwie opcje.

  1. Możesz wysłać zapytanie do bazy danych i pobrać dokładnie to, czego potrzebujesz, aby utworzyć listę, a następnie ponownie wykonać zapytanie, aby pobrać poszczególne szczegóły, które powinny być widoczne na stronie szczegółów.

  2. Możesz wykonać 1 zapytanie, które pobiera wszystkie informacje i buforuje je. Na następnej stronie prośba czytasz z serwerów ram zamiast bazy danych. Jeśli użytkownik wróci do poprzedniej strony lub wybierze następną stronę, nadal wykonujesz zero podróży do bazy danych.

W rzeczywistości sposób, w jaki to wdrażasz, to tylko to i szczegóły implementacji. Jeśli mój największy użytkownik ma 10 zamówień, prawdopodobnie chcę przejść do opcji 2. Jeśli mówię o 10 000 zamówień, potrzebna jest opcja 1. W obu powyższych przypadkach iw wielu innych przypadkach chcę, aby repozytorium ukryło ten szczegół implementacji.

Idąc dalej, jeśli dostanę bilet informujący użytkownika, ile wydali na zamówienia ( dane zagregowane ) w ostatnim miesiącu na stronie z listą zamówień, wolałbym napisać logikę, aby obliczyć to w SQL i zrobić kolejną podróż w obie strony do DB, czy wolisz go obliczyć na podstawie danych, które są już w pamięci RAM serwerów?

Z mojego doświadczenia wynika, że ​​agregaty domen oferują ogromne korzyści.

  • Są to ogromne ponowne użycie kodu części, które faktycznie działa.
  • Upraszczają kod, utrzymując logikę biznesową w warstwie podstawowej, zamiast konieczności przechodzenia przez warstwę infrastruktury, aby zmusić serwer SQL do wykonania tej czynności.
  • Mogą również znacznie przyspieszyć czas reakcji, zmniejszając liczbę zapytań, które należy wykonać, ponieważ można je łatwo buforować.
  • SQL, który piszę, jest często o wiele łatwiejszy w utrzymaniu, ponieważ często tylko pytam o wszystko i obliczam po stronie serwera.
WhiteleyJ
źródło