Zagłębiam się w koncepcje projektowania opartego na domenach (DDD) i odkryłem, że niektóre zasady są dziwne, szczególnie w odniesieniu do izolacji domeny i modelu trwałości. Oto moje podstawowe zrozumienie:
- Usługa w warstwie aplikacji (udostępniająca zestaw funkcji) żąda obiektów domeny z repozytorium, której potrzebuje do wykonywania swojej funkcji.
- Konkretna implementacja tego repozytorium pobiera dane z pamięci, dla której została zaimplementowana
- Usługa informuje obiekt domeny, który zawiera logikę biznesową, o wykonaniu pewnych zadań, które modyfikują jego stan.
- Usługa informuje repozytorium o utrwaleniu zmodyfikowanego obiektu domeny.
- Repozytorium musi odwzorować obiekt domeny z powrotem na odpowiednią reprezentację w pamięci.
Teraz, biorąc pod uwagę powyższe założenia, następujące wydaje się niezręczne:
Ogłoszenie 2 .:
Model domeny wydaje się ładować cały obiekt domeny (w tym wszystkie pola i odwołania), nawet jeśli nie są one potrzebne dla funkcji, która o to poprosiła. Całkowite wczytanie może nawet nie być możliwe, jeśli odwołujemy się do innych obiektów domeny, chyba że załadujesz również te obiekty domeny i wszystkie obiekty, do których się odwołują, itd. I tak dalej. Leniwe ładowanie przychodzi mi na myśl, co oznacza jednak, że zaczynasz sprawdzać obiekty domeny, które powinny przede wszystkim odpowiadać repozytorium.
Biorąc pod uwagę ten problem, „poprawny” sposób ładowania obiektów domeny wydaje się mieć dedykowaną funkcję ładowania dla każdego przypadku użycia. Te dedykowane funkcje ładowałyby wtedy tylko dane wymagane przez przypadek użycia, dla którego zostały zaprojektowane. Oto, gdzie pojawia się niezręczność: po pierwsze musiałbym utrzymać znaczną liczbę funkcji ładowania dla każdej implementacji repozytorium, a obiekty domeny skończyłyby w niekompletnych stanach przenoszących null
swoje pola. Ten ostatni nie powinien technicznie stanowić problemu, ponieważ jeśli wartość nie została załadowana, nie powinna być wymagana przez funkcję, która o to poprosiła. Nadal jest to niewygodne i potencjalne zagrożenie.
Ogłoszenie 3 .:
W jaki sposób obiekt domeny zweryfikowałby ograniczenia unikatowości podczas budowy, jeśli nie ma pojęcia repozytorium? Na przykład, jeśli chciałbym utworzyć nowy User
z unikalnym numerem ubezpieczenia społecznego (który jest podany), najwcześniejszy konflikt wystąpiłby po poproszeniu repozytorium o zapisanie obiektu, tylko jeśli w bazie danych zdefiniowano ograniczenie wyjątkowości. W przeciwnym razie mógłbym poszukać zabezpieczenia User
z danym ubezpieczeniem społecznym i zgłosić błąd, jeśli taki istnieje, zanim utworzę nowy. Ale wtedy kontrole ograniczeń będą działać w usłudze, a nie w obiekcie domeny, do której należą. Właśnie zdałem sobie sprawę, że obiektom domeny bardzo dobrze wolno używać (wstrzykiwanych) repozytoriów do sprawdzania poprawności.
Ogłoszenie 5 .:
Odwzorowanie obiektów domeny na zaplecze pamięci postrzegam jako proces wymagający dużej pracy w porównaniu z bezpośrednią modyfikacją danych domeny przez obiekty domeny. Jest to oczywiście niezbędny warunek oddzielenia konkretnej implementacji pamięci od kodu domeny. Czy jednak rzeczywiście wiąże się to z tak wysokimi kosztami?
Najwyraźniej masz opcję użycia narzędzi ORM do wykonania mapowania. Często wymagałyby one zaprojektowania modelu domeny zgodnie z ograniczeniami ORM, a nawet wprowadzenia zależności od domeny do warstwy infrastruktury (np. Za pomocą adnotacji ORM w obiektach domeny). Przeczytałem również, że ORM wprowadzają znaczny narzut obliczeniowy.
W przypadku baz danych NoSQL, dla których prawie nie istnieją pojęcia podobne do ORM, w jaki sposób śledzisz, które właściwości zmieniły się w modelach domen save()
?
Edycja : Ponadto, aby repozytorium mogło uzyskać dostęp do stanu obiektu domeny (tj. Wartości każdego pola), obiekt domeny musi ujawnić swój stan wewnętrzny, który przerywa enkapsulację.
Ogólnie:
- Dokąd zmierza logika transakcyjna? Z pewnością jest to charakterystyczne dla przetrwania. Niektóre infrastruktury pamięci masowej mogą nawet nie obsługiwać transakcji (np. Fałszywe repozytoria w pamięci).
- Czy w przypadku operacji masowych, które modyfikują wiele obiektów, musiałbym ładować, modyfikować i przechowywać każdy obiekt osobno, aby przejść przez enkapsulowaną logikę sprawdzania poprawności? Jest to przeciwieństwo wykonywania pojedynczego zapytania bezpośrednio w bazie danych.
Byłbym wdzięczny za wyjaśnienia na ten temat. Czy moje założenia są prawidłowe? Jeśli nie, jaki jest właściwy sposób rozwiązania tych problemów?
Odpowiedzi:
Twoje podstawowe zrozumienie jest prawidłowe, a architektura, którą szkicujesz, jest dobra i działa dobrze.
Czytanie między wierszami wydaje się, że pochodzisz z bardziej aktywnego stylu programowania opartego na bazach danych? Aby dostać się do działającego wdrożenia, powiedziałbym, że musisz
1: Obiekty domeny nie muszą zawierać całego wykresu obiektu. Na przykład mógłbym mieć:
Adres i klient muszą być częścią tego samego agregatu, jeśli masz logikę, np. „Nazwa klienta może zaczynać się od tej samej litery, co nazwa domu”. Masz rację, aby uniknąć leniwego ładowania i wersji „Lite” obiektów.
2: Ograniczenia związane z unikalnością dotyczą zasadniczo repozytorium, a nie obiektu domeny. Nie wstrzykuj repozytoriów do Obiektów Domeny, to jest powrót do aktywnego rekordu, po prostu błąd podczas próby zapisania usługi.
Reguła biznesowa nie brzmi: „Nie mogą istnieć jednocześnie dwie instancje użytkownika o tym samym numerze SocialSecurityNumber”
Po prostu nie mogą istnieć w tym samym repozytorium.
3: Nie jest trudno pisać repozytoria zamiast indywidualnych metod aktualizacji właściwości. W rzeczywistości przekonasz się, że i tak masz prawie ten sam kod. Po prostu w której klasie to umieściłeś.
ORM w dzisiejszych czasach są łatwe i nie mają żadnych dodatkowych ograniczeń w kodzie. Powiedziawszy to, osobiście wolę po prostu ręcznie przekręcić SQL. To nie jest takie trudne, nigdy nie napotykasz żadnych problemów z funkcjami ORM i możesz zoptymalizować tam, gdzie jest to wymagane.
Naprawdę nie ma potrzeby śledzenia, które właściwości zmieniły się po zapisaniu. Utrzymuj małe obiekty domeny i po prostu zastąp starą wersję.
Ogólne pytania
Logika transakcji trafia do repozytorium. Ale nie powinieneś mieć dużo, jeśli w ogóle. Pewnie, że potrzebujesz ich, jeśli masz tabele potomne, w których umieszczasz obiekty potomne agregacji, ale zostaną one całkowicie zamknięte w metodzie repozytorium SaveMyObject.
Aktualizacje zbiorcze. Tak, powinieneś osobno zmienić każdy obiekt, a następnie po prostu dodać do swojego repozytorium metodę SaveMyObjects (Lista obiektów), aby dokonać aktualizacji zbiorczej.
Chcesz, aby obiekt domeny lub usługa domeny zawierały logikę. Nie baza danych. Oznacza to, że nie można po prostu „zaktualizować nazwy zestawu klientów = x gdzie y”, ponieważ dla wszystkich, którzy znają obiekt Customer, lub CustomerUpdateService robi 20 dziwnych innych rzeczy po zmianie nazwy.
źródło
AddressId
zamiastAddress
) nie są sprzeczne z zasadami OO?Krótka odpowiedź: Twoje zrozumienie jest prawidłowe, a pytania, które masz, wskazują na ważne problemy, dla których rozwiązania nie są proste ani powszechnie akceptowane.
Punkt 2 .: (ładowanie pełnych wykresów obiektowych)
Nie jestem pierwszym, który podkreśla, że ORM nie zawsze są dobrym rozwiązaniem. Głównym problemem jest to, że ORM nie wiedzą nic o rzeczywistym przypadku użycia, więc nie mają pojęcia, co załadować ani jak zoptymalizować. To jest problem.
Jak powiedziałeś, oczywistym rozwiązaniem jest posiadanie metod trwałości dla każdego przypadku użycia. Ale jeśli nadal używasz do tego ORM, ORM zmusi cię do spakowania wszystkiego w obiekty danych. Co oprócz tego, że tak naprawdę nie jest obiektowe, znowu nie jest najlepszym projektem dla niektórych przypadków użycia.
Co jeśli chcę tylko zbiorczo zaktualizować niektóre rekordy? Dlaczego miałbym potrzebować reprezentacji obiektowej dla wszystkich rekordów? Itp.
Rozwiązaniem tego problemu jest po prostu nie używanie ORM w przypadkach użycia, w których nie jest to dobre dopasowanie. Zaimplementuj przypadek użycia „w naturalny sposób”, który czasami nie wymaga dodatkowej „abstrakcji” samych danych (obiektów danych) ani abstrakcji nad „tabelami” (repozytoriami).
Jak już wspomniałeś, posiadanie do połowy wypełnionych obiektów danych lub zastępowanie odwołań do obiektów „identyfikatorami” to w najlepszym razie obejścia, a nie dobre projekty.
Punkt 3 .: (sprawdzanie ograniczeń)
Jeśli uporczywość nie zostanie wyodrębniona, każdy przypadek użycia może oczywiście sprawdzić dowolne ograniczenie, którego chce. Wymóg, aby obiekty nie znały „repozytorium”, jest całkowicie sztuczny i nie stanowi problemu technologicznego.
Punkt 5 .: (ORM)
Nie, nie ma. Istnieje wiele innych sposobów na wytrwałość. Problem polega na tym, że ORM jest zawsze postrzegany jako „najlepsze” rozwiązanie (przynajmniej w przypadku relacyjnych baz danych). Próba zasugerowania, aby nie używać go w niektórych przypadkach użycia w projekcie, jest daremna, a w zależności od samej ORM czasami nawet niemożliwa, ponieważ narzędzia te czasami używają pamięci podręcznej i opóźnionego wykonania.
Pytanie ogólne 1 .: (transakcje)
Nie sądzę, aby istniało jedno rozwiązanie. Jeśli twój projekt jest zorientowany obiektowo, dla każdego przypadku użycia będzie stosowana metoda „top”. Transakcja powinna tam być.
Wszelkie inne ograniczenia są całkowicie sztuczne.
Pytanie ogólne 2 .: (operacje masowe)
Dzięki ORM jesteś (dla większości ORM, które znam) zmuszony do przechodzenia przez poszczególne obiekty. Jest to całkowicie niepotrzebne i prawdopodobnie nie byłby to twój projekt, gdyby Twoja ręka nie była związana przez ORM.
Wymóg oddzielenia „logiki” od SQL pochodzi z ORM. Oni mają powiedzieć, że ponieważ nie można go obsługiwać. Nie jest to z natury „złe”.
Podsumowanie
Wydaje mi się, że mam na myśli to, że ORM nie zawsze są najlepszym narzędziem dla projektu, a nawet jeśli tak jest, jest mało prawdopodobne, aby był najlepszy dla wszystkich przypadków użycia w projekcie.
Podobnie abstrakcja DDD dla repozytorium danych również nie zawsze jest najlepsza. Powiedziałbym nawet tak daleko, że rzadko są to optymalne projekty.
Nie pozostawia nam to jednego uniwersalnego rozwiązania, więc musielibyśmy pomyśleć o rozwiązaniach dla każdego przypadku użycia osobno, co nie jest dobrą wiadomością i oczywiście utrudnia naszą pracę :)
źródło