W Domain Driven Design, wydaje się, że wiele z umową , że podmioty nie powinny dostęp Repozytoria bezpośrednio.
Czy to pochodzi z książki Erica Evansa Domain Driven Design , czy pochodzi z innych źródeł?
Gdzie jest kilka dobrych wyjaśnień uzasadnienia?
edytuj: Aby wyjaśnić: nie mówię o klasycznej praktyce OO polegającej na oddzielaniu dostępu do danych na osobną warstwę od logiki biznesowej - mówię o konkretnym rozwiązaniu, zgodnie z którym w DDD jednostki nie powinny rozmawiać z danymi warstwa dostępu w ogóle (tzn. nie powinny one zawierać odniesień do obiektów repozytorium)
aktualizacja: Dałem nagrodę BacceSR, ponieważ jego odpowiedź wydawała się najbliższa, ale wciąż jestem całkiem nieźle w tej sprawie. Jeśli jest to tak ważna zasada, to na pewno powinny gdzieś być jakieś dobre artykuły online.
aktualizacja: marzec 2013 r., głosowanie w sprawie pytania sugeruje, że jest to bardzo interesujące i mimo wielu odpowiedzi, nadal uważam, że jest miejsce na więcej, jeśli ludzie mają pomysły na ten temat.
źródło
Odpowiedzi:
Jest tu trochę zamieszania. Repozytoria mają dostęp do zagregowanych katalogów głównych. Zagregowane korzenie to byty. Powodem tego jest rozdzielenie obaw i dobre nakładanie warstw. Nie ma to sensu w przypadku małych projektów, ale jeśli pracujesz w dużym zespole, chcesz powiedzieć: „Uzyskujesz dostęp do produktu za pośrednictwem repozytorium produktów. Produkt jest zagregowanym katalogiem głównym dla zbioru podmiotów, w tym obiektu ProductCatalog. Jeśli chcesz zaktualizować ProductCatalog, musisz przejść przez ProductRepository. ”
W ten sposób masz bardzo, bardzo wyraźny podział na logikę biznesową i miejsca aktualizacji. Nie masz dzieciaka, który jest sam i pisze cały ten program, który robi te wszystkie skomplikowane rzeczy w katalogu produktów, a jeśli chodzi o zintegrowanie go z projektem wyższego szczebla, siedzisz tam, patrząc na to i zdając sobie z tego sprawę wszystko musi zostać porzucone. Oznacza to również, że kiedy ludzie dołączają do zespołu, dodają nowe funkcje, wiedzą, gdzie iść i jak zorganizować program.
Ale poczekaj! Repozytorium odnosi się również do warstwy trwałości, tak jak we wzorcu repozytorium. W lepszym świecie Repozytorium Erica Evansa i wzorzec repozytorium miałyby osobne nazwy, ponieważ często się nakładają. Aby uzyskać wzorzec repozytorium, masz kontrast z innymi sposobami uzyskiwania dostępu do danych, za pomocą magistrali usług lub systemu modeli zdarzeń. Zwykle po przejściu na ten poziom definicja Repozytorium Erica Evansa jest na marginesie i zaczynasz mówić o ograniczonym kontekście. Każdy ograniczony kontekst jest zasadniczo własną aplikacją. Być może masz wyrafinowany system zatwierdzania umożliwiający umieszczanie rzeczy w katalogu produktów. W oryginalnym projekcie produkt był centralnym elementem, ale w tym ograniczonym kontekście katalog produktów jest. Nadal możesz uzyskiwać dostęp do informacji o produkcie i aktualizować produkt za pośrednictwem magistrali usług,
Powrót do pierwotnego pytania. Jeśli uzyskujesz dostęp do repozytorium z poziomu encji, oznacza to, że encja nie jest tak naprawdę jednostką biznesową, ale prawdopodobnie czymś, co powinno istnieć w warstwie usług. Wynika to z faktu, że podmioty są obiektami biznesowymi i powinny zajmować się jak najbardziej zbliżonym do DSL (językiem specyficznym dla domeny). W tej warstwie znajdują się tylko informacje biznesowe. Jeśli rozwiążesz problem z wydajnością, będziesz musiał szukać gdzie indziej, ponieważ powinny tu znajdować się tylko informacje biznesowe. Jeśli nagle pojawią się problemy z aplikacją, bardzo utrudniasz rozszerzenie i utrzymanie aplikacji, która tak naprawdę jest sercem DDD: tworzenie oprogramowania, które można utrzymać.
Odpowiedź na komentarz 1 : Racja, dobre pytanie. Dlatego nie cała weryfikacja zachodzi w warstwie domeny. Sharp ma atrybut „DomainSignature”, który robi to, co chcesz. Jest świadomy trwałości, ale bycie atrybutem utrzymuje warstwę domeny w czystości. Zapewnia to, że nie masz zduplikowanego bytu, w twoim przykładzie o tej samej nazwie.
Porozmawiajmy jednak o bardziej skomplikowanych zasadach sprawdzania poprawności. Powiedzmy, że jesteś Amazon.com. Czy kiedykolwiek zamówiłeś coś z wygasłą kartą kredytową? Mam, gdzie nie zaktualizowałem karty i coś kupiłem. Przyjmuje zamówienie, a interfejs informuje mnie, że wszystko jest brzoskwiniowe. Około 15 minut później otrzymam wiadomość e-mail z informacją, że wystąpił problem z moim zamówieniem, moja karta kredytowa jest nieważna. To, co się tutaj dzieje, polega na tym, że w warstwie domeny istnieje pewna walidacja wyrażeń regularnych. Czy to prawidłowy numer karty kredytowej? Jeśli tak, zachowaj kolejność. Istnieje jednak dodatkowa weryfikacja w warstwie zadań aplikacji, w której sprawdzana jest usługa zewnętrzna, aby sprawdzić, czy można dokonać płatności za pomocą karty kredytowej. Jeśli nie, nie wysyłaj niczego, zawieś zamówienie i poczekaj na klienta.
Nie bój się tworzyć obiektów sprawdzania poprawności w warstwie usług, które mogą uzyskiwać dostęp do repozytoriów. Po prostu trzymaj go poza warstwą domeny.
źródło
Na początku przekonałem się, aby umożliwić niektórym z moich podmiotów dostęp do repozytoriów (tj. Leniwe ładowanie bez ORM). Później doszedłem do wniosku, że nie powinienem i że mogę znaleźć alternatywne sposoby:
Vernon Vaughn w czerwonej książce Implementing Domain-Driven Design odnosi się do tego problemu w dwóch znanych mi miejscach (uwaga: ta książka jest w pełni poparta przez Evansa, jak można przeczytać we wstępie). W rozdziale 7 dotyczącym usług używa usługi domenowej i specyfikacji, aby obejść potrzebę użycia agregatu do korzystania z repozytorium i innego agregatu do ustalenia, czy użytkownik jest uwierzytelniony. Cytowany jest jako:
Vernon, Vaughn (06.02.2013). Wdrażanie projektowania opartego na domenie (lokalizacja Kindle 6089). Edukacja Pearson. Wersja Kindle.
A w rozdziale 10 na temat agregatów, w części zatytułowanej „Nawigacja modelu” , mówi (zaraz po tym, jak zaleca użycie globalnych unikalnych identyfikatorów do odwoływania się do innych zagregowanych pierwiastków):
On pokazuje przykład tego w kodzie:
Następnie wspomina o jeszcze innym rozwiązaniu, w jaki sposób usługa domeny może być używana w metodzie komend agregujących wraz z podwójną wysyłką . (Nie mogę zalecić, na ile korzystne jest czytanie jego książki. Po zmęczeniu się niekończącym się szperaniem w Internecie, przejrzyj zasłużone pieniądze i przeczytaj książkę).
Następnie przeprowadziłem dyskusję z zawsze wdzięcznym Marco Pivetta @Ocramius, który pokazał mi trochę kodu po wyciągnięciu specyfikacji z domeny i użyciu tego:
1) Nie jest to zalecane:
2) W przypadku usługi domenowej jest to dobre:
źródło
getFriends()
zanim zrobisz cokolwiek innego, będzie pusty lub załadowany leniwie. Jeśli pusty, to ten obiekt leży i jest w nieprawidłowym stanie. Masz jakieś przemyślenia na ten temat?To bardzo dobre pytanie. Nie mogę się doczekać dyskusji na ten temat. Ale myślę, że wspomniano o tym w kilku książkach o DDD, Jimmy Nilssons i Eric Evans. Myślę, że jest to również widoczne w przykładach użycia wzorca reposistorii.
ALE pozwala dyskutować. Myślę, że bardzo słuszną myślą jest to, dlaczego istota powinna wiedzieć, jak zachować inną istotę? Ważne w przypadku DDD jest to, że każda jednostka ma obowiązek zarządzania własną „sferą wiedzy” i nie powinna wiedzieć nic o tym, jak czytać lub pisać inne jednostki. Pewnie, że prawdopodobnie możesz po prostu dodać interfejs repozytorium do Jednostki A w celu odczytania Jednostek B. Ale istnieje ryzyko, że ujawnisz wiedzę na temat tego, jak zachować B. Czy jednostka A również wykona walidację na B przed utrwaleniem B w db?
Jak widać, jednostka A może bardziej zaangażować się w cykl życia jednostki B, co może zwiększyć złożoność modelu.
Wydaje mi się (bez żadnego przykładu), że testy jednostkowe będą bardziej złożone.
Ale jestem pewien, że zawsze będą scenariusze, w których masz ochotę korzystać z repozytoriów za pośrednictwem podmiotów. Musisz spojrzeć na każdy scenariusz, aby dokonać właściwej oceny. Plusy i minusy. Ale moim zdaniem rozwiązanie repozytorium-jednostka zaczyna się od wielu wad. To musi być bardzo szczególny scenariusz z Plusami, który równoważy Wady ....
źródło
Po co wydzielać dostęp do danych?
Wydaje mi się, że na podstawie dwóch pierwszych stron rozdziału Model Driven Design znajduje się uzasadnienie, dlaczego chcesz wyodrębnić szczegóły techniczne implementacji z modelu domeny.
Wszystko to wydaje się mieć na celu uniknięcie osobnego „modelu analizy”, który zostaje oddzielony od faktycznego wdrożenia systemu.
Z tego, co rozumiem z tej książki, wynika, że ten „model analizy” może ostatecznie zostać zaprojektowany bez uwzględnienia implementacji oprogramowania. Gdy programiści próbują wdrożyć model rozumiany przez stronę biznesową, tworzą własne abstrakcje z konieczności, powodując ściankę w komunikacji i zrozumieniu.
Z drugiej strony programiści, którzy wprowadzają zbyt wiele problemów technicznych do modelu domeny, mogą również powodować ten podział.
Można więc wziąć pod uwagę, że praktykowanie oddzielenia problemów, takich jak wytrwałość, może pomóc zabezpieczyć się przed zaprojektowaniem rozbieżnych modeli analizy. Jeśli wprowadzenie do modelu czegoś takiego jak wytrwałość wydaje się konieczne, oznacza to czerwoną flagę. Być może model nie jest praktyczny do wdrożenia.
Cytowanie:
„Pojedynczy model zmniejsza ryzyko błędu, ponieważ projekt jest teraz bezpośrednim skutkiem starannie przemyślanego modelu. Projekt, a nawet sam kod, ma komunikatywność modelu”.
Sposób, w jaki to interpretuję, jeśli skończyłeś z większą liczbą linii kodu zajmujących się takimi kwestiami jak dostęp do bazy danych, tracisz komunikatywność.
Jeśli potrzeba dostępu do bazy danych dotyczy między innymi sprawdzania unikatowości, spójrz na:
Udi Dahan: największe błędy popełniane przez zespoły przy stosowaniu DDD
http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/
w sekcji „Wszystkie reguły nie są sobie równe”
i
Wykorzystanie wzorca modelu domeny
http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119
w części „Scenariusze nieużywania modelu domeny”, która dotyczy tego samego tematu.
Jak oddzielić dostęp do danych
Ładowanie danych przez interfejs
„Warstwa dostępu do danych” została wydzielona przez interfejs, który wywołujesz w celu pobrania wymaganych danych:
Plusy: Interfejs oddziela kod hydrauliczny „dostęp do danych”, pozwalając na pisanie testów. Dostęp do danych może być obsługiwany indywidualnie dla każdego przypadku, co zapewnia lepszą wydajność niż ogólna strategia.
Wady: kod wywołujący musi zakładać, co zostało załadowane, a co nie.
Powiedzmy, że GetOrderLines zwraca obiekty OrderLine z zerową właściwością ProductInfo ze względu na wydajność. Deweloper musi mieć dogłębną znajomość kodu kryjącego się za interfejsem.
Wypróbowałem tę metodę na prawdziwych systemach. Ostatecznie zmieniasz zakres tego, co jest ładowane przez cały czas, próbując naprawić problemy z wydajnością. W końcu zaglądasz za interfejs, aby zobaczyć kod dostępu do danych i zobaczyć, co jest ładowane, a co nie.
Teraz rozdzielenie problemów powinno pozwolić deweloperowi skupić się na jednym aspekcie kodu naraz, o ile to możliwe. Technika interfejsu usuwa W JAKI sposób ładowane są te dane, ale NIE W JAKI SPOSÓB ładowane są DUŻE dane, KIEDY są ładowane i GDZIE są ładowane.
Wniosek: dość niska separacja!
Powolne ładowanie
Dane są ładowane na żądanie. Wywołania w celu załadowania danych są ukryte w samym grafie obiektowym, gdzie dostęp do właściwości może spowodować wykonanie zapytania SQL przed zwróceniem wyniku.
Plusy: „KIEDY, GDZIE I JAK” dostęp do danych jest ukryty przed deweloperem skupiającym się na logice domeny. W agregacie nie ma kodu zajmującego się ładowaniem danych. Ilość załadowanych danych może być dokładną ilością wymaganą przez kod.
Wady: gdy pojawia się problem z wydajnością, trudno jest go naprawić, gdy masz ogólne rozwiązanie „jeden rozmiar dla wszystkich”. Leniwe ładowanie może ogólnie pogorszyć wydajność, a wdrożenie leniwego ładowania może być trudne.
Interfejs ról / chętne pobieranie
Każdy przypadek użycia jest jawnie wyrażony za pomocą interfejsu roli zaimplementowanego przez klasę zagregowaną, umożliwiając obsługę strategii ładowania danych dla każdego przypadku użycia.
Strategia pobierania może wyglądać następująco:
Wówczas Twój agregat może wyglądać następująco:
BillOrderFetchingStrategy służy do budowania agregatu, a następnie agregat wykonuje swoją pracę.
Zalety: Pozwala na niestandardowy kod dla każdego przypadku użycia, pozwalając na optymalną wydajność. Jest zgodny z zasadą segregacji interfejsu . Brak wymagań dotyczących kodu złożonego. Testy jednostkowe agregatów nie muszą naśladować strategii ładowania. W większości przypadków można zastosować ogólną strategię ładowania (np. Strategię „wczytaj wszystko”), a w razie potrzeby można wdrożyć specjalne strategie ładowania.
Minusy: programista wciąż musi modyfikować / weryfikować strategię pobierania po zmianie kodu domeny.
Dzięki strategii pobierania możesz wciąż zmieniać niestandardowy kod pobierania w celu zmiany reguł biznesowych. Nie jest to idealna separacja problemów, ale będzie łatwiejsza w utrzymaniu i jest lepsza niż pierwsza opcja. Strategia pobierania zawiera dane HOW, WHEN i WHERE wczytywane. Ma lepszą separację problemów, bez utraty elastyczności, tak jak jeden rozmiar pasuje do wszystkich leniwych metod ładowania.
źródło
Znalazłem na tym blogu całkiem dobre argumenty przeciwko enkapsulowaniu repozytoriów wewnątrz jednostek:
http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities
źródło
Co za wspaniałe pytanie. Jestem na tej samej ścieżce odkrywania i większość odpowiedzi w Internecie wydaje się przynosić tyle problemów, ile przynosi rozwiązania.
Tak więc (na ryzyko napisania czegoś, z czym nie zgadzam się za rok) oto moje dotychczasowe odkrycia.
Przede wszystkim podoba nam się bogaty model domeny , który zapewnia nam wysoką wykrywalność (tego, co możemy zrobić z agregacją) i czytelność (ekspresyjne wywołania metod).
Chcemy to osiągnąć bez wprowadzania jakichkolwiek usług do konstruktora jednostki, ponieważ:
Jak więc możemy to zrobić? Do tej pory doszedłem do wniosku, że zależności od metod i podwójna wysyłka zapewniają godne rozwiązanie.
CreateCreditNote()
wymaga teraz usługi odpowiedzialnej za tworzenie not kredytowych. Wykorzystuje podwójną wysyłkę , w pełni odciążając pracę do odpowiedzialnej usługi, przy jednoczesnym zachowaniu wykrywalności odInvoice
jednostki.SetStatus()
teraz ma prostą zależność od programu rejestrującego, który oczywiście wykona część pracy .W tym drugim przypadku, aby ułatwić kod klienta, możemy zamiast tego zalogować się za pomocą
IInvoiceService
. W końcu rejestrowanie faktur wydaje się dość nieodłącznym elementem faktury. Taki pojedynczyIInvoiceService
pomaga uniknąć potrzeby wszelkiego rodzaju mini-usług dla różnych operacji. Minusem jest to, że niejasne jest, co dokładnie zrobi ta usługa . Może nawet zacząć wyglądać na podwójną wysyłkę, podczas gdy większość pracy jest nadal wykonywanaSetStatus()
sama w sobie.Wciąż moglibyśmy nazwać parametr „logger”, mając nadzieję na ujawnienie naszych zamiarów. Wydaje się jednak trochę słaby.
Zamiast tego wolałbym poprosić o
IInvoiceLogger
(jak to już zrobiliśmy w przykładzie kodu) iIInvoiceService
wdrożyć ten interfejs. Kod klienta może po prostu użyć jego pojedynczegoIInvoiceService
kodu dla wszystkichInvoice
metod, które wymagają jakiejkolwiek tak szczególnej, „faktury usługowej” związanej z fakturami, podczas gdy podpisy metod wciąż wyraźnie wyjaśniają, o co proszą.Zauważam, że nie zwracałem się do repozytoriów w odpowiedni sposób. Cóż, program rejestrujący jest repozytorium lub korzysta z niego, ale pozwól, że podam również bardziej wyraźny przykład. Możemy zastosować to samo podejście, jeśli repozytorium jest potrzebne tylko w jednej lub dwóch metodach.
W rzeczywistości stanowi to alternatywę dla zawsze kłopotliwych leniwych ładunków .
Aktualizacja: zostawiłem poniższy tekst do celów historycznych, ale sugeruję 100% unikanie leniwych ładunków.
Dla prawdziwych, opartych na własności leniwych obciążeń, ja nie wykorzystują obecnie konstruktora wtrysku, ale w sposób utrwalania-ignorantami.
Z jednej strony repozytorium ładujące dane
Invoice
z bazy danych może mieć swobodny dostęp do funkcji, która załaduje odpowiednie noty kredytowe i wstrzyknie tę funkcję doInvoice
.Z drugiej strony, kod, który tworzy rzeczywistą nową,
Invoice
po prostu przekaże funkcję, która zwraca pustą listę:(Zwyczaj
ILazy<out T>
mógłby nas uwolnić od brzydkiej obsadyIEnumerable
, ale skomplikowałoby to dyskusję).Z przyjemnością usłyszę twoje opinie, preferencje i ulepszenia!
źródło
Wydaje mi się, że jest to ogólnie dobra praktyka związana z OOD, a nie specyficzna dla DDD.
Powody, o których mogę myśleć to:
źródło
po prostu Vernon Vaughn daje rozwiązanie:
źródło
Nauczyłem się kodować programowanie obiektowe, zanim pojawi się całe to oddzielne brzęczenie warstwy, a moje pierwsze obiekty / klasy DID odwzorowują bezpośrednio do bazy danych.
W końcu dodałem warstwę pośrednią, ponieważ musiałem przeprowadzić migrację na inny serwer bazy danych. Kilka razy widziałem / słyszałem o tym samym scenariuszu.
Myślę, że oddzielenie dostępu do danych (aka „Repozytorium”) od logiki biznesowej jest jedną z tych rzeczy, które zostały kilkakrotnie wymyślone na nowo, pomyślała książka Domain Driven Design, sprawiają, że jest to dużo „szumu”.
Obecnie używam 3 warstw (GUI, logika, dostęp do danych), podobnie jak wielu programistów, ponieważ jest to dobra technika.
Rozdzielenie danych na
Repository
warstwę (zwaną teżData Access
warstwą) może być postrzegane jako dobra technika programowania, a nie tylko reguła.Podobnie jak wiele metodologii, możesz zacząć, NIE zaimplementowany, i ostatecznie zaktualizować swój program, gdy tylko je zrozumiesz.
Cytat: Iliada nie została całkowicie wynaleziona przez Homera, Carmina Burana nie została całkowicie wymyślona przez Carla Orffa, aw obu przypadkach osoba, która zatrudniała innych, razem, otrzymała uznanie ;-)
źródło
To stare rzeczy. Książka Erica sprawiła, że trochę się rozgorzała.
Powód jest prosty - ludzki umysł słabnie, gdy staje w obliczu niejasno powiązanych wielu kontekstów. Prowadzą do dwuznaczności (Ameryka w Ameryce Południowej / Północnej oznacza Amerykę Południową / Północną), niejednoznaczność prowadzi do ciągłego mapowania informacji za każdym razem, gdy umysł „dotyka” tego, co podsumowuje się jako zła produktywność i błędy.
Logika biznesowa powinna być odzwierciedlona tak wyraźnie, jak to możliwe. Klucze obce, normalizacja, mapowanie relacyjne obiektów pochodzą z zupełnie innej dziedziny - są to kwestie techniczne, związane z komputerem.
Analogicznie: jeśli uczysz się pisma odręcznego, nie powinieneś być obciążony zrozumieniem, gdzie powstał pióro, dlaczego atrament trzyma się na papierze, kiedy wynaleziono papier i jakie są inne słynne chińskie wynalazki.
Powód jest nadal taki sam, o którym wspomniałem powyżej. Tutaj jest tylko krok dalej. Dlaczego byty powinny częściowo ignorować upór, skoro mogą być (przynajmniej blisko) całkowicie? Nasz model ma mniej problemów niezwiązanych z dziedziną - więcej oddechu nasz umysł otrzymuje, gdy musi go ponownie zinterpretować.
źródło
Cytując Carolina Lilientahl, „Wzory powinny zapobiegać cyklom” https://www.youtube.com/watch?v=eJjadzMRQAk , gdzie odnosi się do cyklicznych zależności między klasami. W przypadku repozytoriów wewnątrz agregatów istnieje pokusa, aby tworzyć cykliczne zależności z powodu braku nawigacji po obiektach jako jedynego powodu. Wzorzec wspomniany powyżej przez prograhammer, który był zalecany przez Vernona Vaughna, w którym do innych agregatów odwołują się identyfikatory zamiast instancji root (czy istnieje nazwa dla tego wzorca?) Sugeruje alternatywę, która mogłaby prowadzić do innych rozwiązań.
Przykład cyklicznej zależności między klasami (spowiedź):
(Time0): Dwie klasy, Sample i Well, odnoszą się do siebie (zależność cykliczna). Studnia odnosi się do próbki, a próbka odwołuje się do studni, dla wygody (czasami zapętla próbki, a czasami zapętla wszystkie dołki w płytce). Nie wyobrażałem sobie przypadków, w których Próbka nie odwoływałaby się do studni, w której jest umieszczona.
(Czas1): Rok później wdrożono wiele przypadków użycia .... i są teraz przypadki, w których Próbka nie powinna odwoływać się do studni, w której jest umieszczona. W ramach jednego kroku znajdują się płytki tymczasowe. Tutaj studnia odnosi się do próbki, która z kolei odnosi się do studni na innej płytce. Z tego powodu czasami zachowuje się dziwne zachowanie, gdy ktoś próbuje wdrożyć nowe funkcje. Penetracja wymaga czasu.
Pomógł mi również wspomniany wyżej artykuł o negatywnych aspektach leniwego ładowania.
źródło
W idealnym świecie DDD proponuje, aby jednostki nie miały odniesienia do warstw danych. ale nie żyjemy w idealnym świecie. Domeny mogą wymagać odwoływania się do innych obiektów domeny dla logiki biznesowej, z którymi mogą nie być zależne. Logiczne jest, aby jednostki odwoływały się do warstwy repozytorium wyłącznie w celu odczytu, aby pobrać wartości.
źródło