Czy izolacja domeny / modelu trwałości jest zwykle tak niewygodna?

13

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:

  1. Usługa w warstwie aplikacji (udostępniająca zestaw funkcji) żąda obiektów domeny z repozytorium, której potrzebuje do wykonywania swojej funkcji.
  2. Konkretna implementacja tego repozytorium pobiera dane z pamięci, dla której została zaimplementowana
  3. Usługa informuje obiekt domeny, który zawiera logikę biznesową, o wykonaniu pewnych zadań, które modyfikują jego stan.
  4. Usługa informuje repozytorium o utrwaleniu zmodyfikowanego obiektu domeny.
  5. Repozytorium musi odwzorować obiekt domeny z powrotem na odpowiednią reprezentację w pamięci.

Ilustracja przepływu

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 nullswoje 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 Userz 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 Userz 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?

Double M
źródło
1
Dobre punkty i pytania, interesują mnie również te. Jedna uwaga dodatkowa - jeśli właściwie modelujesz agregat, co oznacza, że ​​w danym momencie istnienia instancja agregacji musi być w poprawnym stanie - to jest główny punkt agregatu (i nie należy używać agregatu jako kontenera kompozycji). Oznacza to również, że aby przywrócić agregację z danych DB, samo repozytorium zwykle musiałoby używać określonego konstruktora i zestawu operacji mutacji, a ja nie rozumiem, w jaki sposób ORM mógłby automatycznie wiedzieć, jak wykonać te operacje .
Dusan
2
Jeszcze bardziej rozczarowujące jest to, że pytania takie jak twoje są zadawane dość często, ale o ile mi wiadomo - istnieją przykłady ZERO implementacji agregatów i repozytoriów według książki
Dusan

Odpowiedzi:

5

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ć:

public class Customer
{
    public string AddressId {get;set;}
    public string Name {get;set;}
}

public class Address
{
    public string Id {get;set;}
    public string HouseNumber {get;set;
}

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

  1. 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.

  2. 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.

Ewan
źródło
Świetna odpowiedź. Masz całkowitą rację, jestem przyzwyczajony do aktywnego stylu kodowania rekordów, dlatego wzorzec repozytorium wydaje się dziwny na pierwszy rzut oka. Czy jednak obiekty „lean” ( AddressIdzamiast Address) nie są sprzeczne z zasadami OO?
Podwójne M
nie, nadal masz obiekt Adres, to po prostu nie dziecko potomka
Ewan
mapowanie obiektów reklamowych bez śledzenia zmian softwareengineering.stackexchange.com/questions/380274/…
Double M
2

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)

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?

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ę :)

Robert Bräutigam
źródło
Bardzo interesujące punkty, dziękuję za potwierdzenie moich założeń. Powiedziałeś, że istnieje wiele innych sposobów na wytrwałość. Czy możesz polecić skuteczny wzorzec projektowy do użycia z bazami danych grafów (bez ORM), który nadal zapewnia PI?
Podwójne M
1
Właściwie chciałbym zapytać, czy potrzebujesz izolacji (i jakiego rodzaju) w pierwszej kolejności. Izolowanie według technologii (tj. Bazy danych, interfejsu użytkownika itp.) Przynosi prawie automatycznie „niezręczność”, której próbujesz uniknąć, z korzyścią nieco łatwiejszej wymiany technologii bazy danych. Koszt jest jednak trudniejszą zmianą logiki biznesowej, ponieważ rozkłada się ona między warstwami. Lub możesz podzielić funkcje biznesowe, co utrudniłoby zmianę bazy danych, ale łatwiejszą zmianę logiki. Które naprawdę chcesz?
Robert Bräutigam
1
Możesz uzyskać najlepszą wydajność, jeśli po prostu modelujesz domenę (tj. Funkcje biznesowe) i nie wyodrębniasz bazy danych (czy relacyjny czy graficzny nie ma znaczenia). Ponieważ baza danych nie jest wyodrębniona z przypadku użycia, to przypadek użycia może zaimplementować najbardziej optymalne zapytania / aktualizacje, jakie chce, i nie musi przechodzić przez niewygodny model obiektowy, aby osiągnąć to, czego chce.
Robert Bräutigam
Cóż, głównym celem jest unikanie obaw związanych z trwałością z dala od logiki biznesowej, aby mieć czysty kod, który jest łatwy do zrozumienia, rozszerzenia i przetestowania. Możliwość wymiany technologii DB to tylko bonus. Widzę, że istnieje oczywiste tarcie między wydajnością a ignorancją, która wydaje się silniejsza w przypadku baz danych wykresu ze względu na potężne zapytania, których możesz (ale nie wolno) używać.
Podwójne M
1
Jako programista Java Enterprise mogę powiedzieć, że próbowaliśmy oddzielić wytrwałość od logiki przez ostatnie dwie dekady. To nie działa Po pierwsze, separacja nigdy tak naprawdę nie została osiągnięta. Nawet dzisiaj istnieje wiele rzeczy związanych z bazą danych w rzekomo „biznesowych” obiektach, z których głównym jest identyfikator bazy danych (i wiele adnotacji do bazy danych). Po drugie, jak powiedziałeś, czasami logika biznesowa jest wykonywana w bazie danych w obu kierunkach. Po trzecie, dlatego mamy specjalne bazy danych, aby móc odciążyć logikę najlepiej wykonać tam, gdzie są dane.
Robert Bräutigam