Najlepsze praktyki dotyczące mapowania DTO na obiekt domeny?

83

Widziałem wiele pytań związanych z mapowaniem DTO na obiekty domeny, ale nie czułem, że odpowiedziały na moje pytanie. Używałem już wielu metod i mam własne opinie, ale szukam czegoś bardziej konkretnego.

Sytuacja:

Mamy wiele obiektów domeny. Używamy modelu CSLA, więc nasze obiekty domeny mogą być dość złożone i zawierają własny dostęp do danych. Nie chcesz ich przekazywać na drucie. Będziemy pisać nowe usługi, które będą zwracać dane w wielu formatach (.Net, JSON itp.). Z tego (i innych powodów) tworzymy również niewielki obiekt do przesyłania danych, który będzie przekazywany po kablu.

Moje pytanie brzmi: jak połączyć DTO i obiekt domeny?

Moją pierwszą reakcją jest użycie rozwiązania Fowler, typu DTO . Widziałem to wiele razy i wydaje mi się to właściwe. Obiekt domeny nie zawiera żadnego odniesienia do DTO. Jednostka zewnętrzna („mapper” lub „assembler”) jest wywoływana w celu utworzenia DTO z obiektu domeny. Zwykle jest ORM po stronie obiektu domeny . Wadą tego jest to, że „twórca map” staje się niezwykle złożony w każdej rzeczywistej sytuacji i może być bardzo delikatny.

Innym wysuniętym pomysłem jest to, aby obiekt domeny „zawierał” DTO, ponieważ jest to po prostu ubogi obiekt danych. Właściwości obiektu domeny odwołują się wewnętrznie do właściwości DTO i mogą po prostu zwrócić DTO, jeśli zostanie o to poproszony. Nie widzę z tym żadnych problemów, ale czuję się źle. Widziałem artykuły, w których ludzie używają NHibernate wydają się używać tej metody.

Czy są inne sposoby? Czy warto skorzystać z jednego z powyższych sposobów? Jeśli tak, a jeśli nie, dlaczego?

Brian Ellis
źródło
4
Automapper wygląda interesująco. Widziałem dużo kodu, który zostałby zastąpiony. Moim głównym problemem jest to, że jeśli z jakiegoś powodu utknę z mnóstwem kodu mapującego, wolałbym mieć nad nim kontrolę.
Brian Ellis,
2
Kiedy przechodzimy od DTO do obiektów domeny, mapowanie to jest w 100% ręczne. Jest to znacznie trudniejszy problem do rozwiązania, ponieważ staramy się, aby nasze obiekty domeny były oparte na operacjach, a nie tylko na kontenerach danych. Przechodzenie do DTO to łatwy problem do rozwiązania.
Jimmy Bogard
Inną opcją jest wersja beta ServiceToolkit.NET, którą rozpoczęliśmy podczas naszego ostatniego projektu. Może to ci pomoże: http://servicetoolkit.codeplex.com/
Zgodziłbym się, że błędem jest, aby obiekt domeny nie miał wiedzy o obiekcie dto. Chociaż w tym przypadku mogą być powiązane, ich cel jest całkowicie oddzielny (dtos są zwykle tworzone do określonego celu) i tworzyłbyś niepotrzebną zależność.
Sinaesthetic

Odpowiedzi:

40

Korzyści z posiadania programu mapującego, który znajduje się między Twoją domeną a DTO, nie są tak oczywiste, gdy obsługujesz tylko jedno mapowanie, ale wraz ze wzrostem liczby mapowań izolowanie tego kodu od domeny pomaga utrzymać prostszą i szczuplejszą domenę. Nie będziesz zaśmiecać swojej domeny zbyt dużą wagą.

Osobiście staram się trzymać mapowanie z dala od jednostek domeny i umieszczam odpowiedzialność na tym, co nazywam „warstwą menedżera / usług”. Jest to warstwa, która znajduje się między aplikacją a repozytorium (ami) i zapewnia logikę biznesową, taką jak koordynacja przepływu pracy (jeśli zmodyfikujesz A, może być konieczne zmodyfikowanie również B, aby usługa A działała z usługą B).

Gdybym miał wiele możliwych formatów końcowych, mógłbym spojrzeć na stworzenie modułu formatującego z możliwością wtyczki, który mógłby używać wzorca Visitor, na przykład do przekształcania moich encji, ale nie znalazłem jeszcze potrzeby na coś tak złożonego.

JoshBerke
źródło
„(Jeśli zmodyfikujesz A, być może będziesz musiał zmodyfikować również B, aby usługa A działała z usługą B)” - Czy to nie podlega logice biznesowej? Myślę, że ta część powinna trafić raczej do kontrolera niż do serwisu?
Ayyappa,
24

Możesz użyć automappera, takiego jak ten napisany przez Jimmy'ego Bogarda, który nie ma połączenia między obiektami i opiera się na przestrzeganiu konwencji nazewnictwa.

Garry Shutler
źródło
9
Automapper może doprowadzić do przypadkowego odsłonięcia właściwości -> luki w zabezpieczeniach. Lepiej byłoby wyraźnie powiedzieć, co powinno być ujawniane jako DTO.
deamon
4
@deamon: uzasadniona obawa, ale tak samo jest z błędami (i potencjalnymi lukami w zabezpieczeniach spowodowanymi nadzorem człowieka), które można utworzyć, pisząc cały ten lepki kod mapowania. Pójdę na drogę automagii i obsłużę 5%, używając wbudowanej funkcji niestandardowego mapowania.
Merritt
@deamon - czy nie możesz po prostu wykonać mapowania warunkowego dla tych właściwości, których nie powinieneś ujawniać? Myślisz, że AutoMapper poradzi sobie z takim scenariuszem?
Richard B
Jeśli używasz AutoMapper, myślę, że niezwykle ważne jest, aby mieć wszystkie testy jednostkowe na miejscu, aby sprawdzić, czy mapowanie jest wykonane poprawnie.
L-Four
8

Utrzymywanie logiki mapowania wewnątrz encji oznacza, że ​​obiekt domeny jest teraz świadomy „szczegółów implementacji”, o których nie musi wiedzieć. Ogólnie DTO jest Twoją bramą do świata zewnętrznego (z przychodzącego żądania lub poprzez odczyt z zewnętrznej usługi / bazy danych). Ponieważ encja jest częścią logiki biznesowej, prawdopodobnie najlepiej jest trzymać te szczegóły poza jednostką.

Pozostawienie mapowania gdzie indziej byłoby jedyną alternatywą - ale gdzie to powinno iść? Próbowałem wprowadzić mapowanie obiektów / usług, ale po wszystkim, co zostało powiedziane i zrobione, wydawało się, że jest to nadmierna inżynieria (i prawdopodobnie tak było). Odniosłem pewien sukces, używając Automappera i innych w mniejszych projektach, ale narzędzia takie jak Automapper mają swoje własne pułapki. Miałem dość trudne do znalezienia problemy związane z mapowaniami, ponieważ mapowania Automappera są niejawne i całkowicie oddzielone od reszty twojego kodu (nie jak „oddzielenie problemów”, ale bardziej jak „gdzie działa zapomniane przez Boga mapowanie”), więc oni czasami może być trudno wyśledzić. Nie znaczy to, że Automapper nie ma swoich zastosowań, ponieważ ma. Po prostu uważam, że mapowanie powinno być czymś tak oczywistym i przejrzystym, jak to tylko możliwe, aby uniknąć problemów.

Zamiast tworzyć warstwę usług mapowania, odniosłem duży sukces, utrzymując moje mapowania w moich DTO. Ponieważ DTO zawsze znajduje się na granicy aplikacji, można im uświadomić obiekt biznesowy i dowiedzieć się, jak mapować z / do nich. Nawet jeśli liczba mapowań jest skalowana do rozsądnej ilości, działa to bezproblemowo. Wszystkie mapowania znajdują się w jednym miejscu i nie musisz zarządzać wieloma usługami mapowania w warstwie danych, warstwie antykorupcyjnej lub warstwie prezentacji. Zamiast tego mapowanie jest po prostu szczegółem implementacji delegowanym do DTO zaangażowanego w żądanie / odpowiedź. Ponieważ serializatory generalnie serializują tylko właściwości i pola, gdy wysyłasz je przez sieć, nie powinieneś napotykać żadnych problemów. Osobiście uważam, że jest to najczystsza opcja i mogę powiedzieć, że z mojego doświadczenia

Kyle Goode
źródło
7

Używamy szablonów T4 do tworzenia klas mapowania.

Pro's - czytelny dla człowieka kod dostępny w czasie kompilacji, szybszy niż program mapujący w czasie wykonywania. 100% kontrola nad kodem (możliwość wykorzystania częściowych metod / wzorca szablonu w celu rozszerzenia funkcjonalności na zasadzie ad-hoc)

Wady - wykluczenie pewnych właściwości, kolekcji obiektów domeny itp., Nauka składni T4.

SturmUndDrang
źródło
3

Jak widzisz implementację konstruktora wewnątrz klasy DTO, który przyjmuje jako parametr obiekt domeny?

Powiedz ... Coś w tym stylu

class DTO {

     // attributes 

     public DTO (DomainObject domainObject) {
          this.prop = domainObject.getProp();
     }

     // methods
}
Zwycięzca
źródło
10
Proszę, nigdy tego nie rób. Nie chcesz, aby Twoja warstwa DTO była świadoma lub zależna od warstwy domeny. Zaletą mapowania jest to, że niższe warstwy można łatwo przełączać, zmieniając mapowanie, lub że modyfikacje w dolnej warstwie można kontrolować, zmieniając mapowanie. Powiedzmy, że dtoA mapuje dziś domainObjectA, ale jutro wymagane jest, aby było mapowane na domainObjectB. W twoim przypadku musisz zmodyfikować obiekt DTO, co jest wielkim nie-nie. Straciłeś wiele zalet twórcy map.
Frederik Prijck
2
Po pierwsze, dzięki! :RE. Więc @FrederikPrijck, wstawiając warstwę między the DTOi the DomainObject, w zasadzie staramy się rozwiązać ten problem DTO zależy od obiektu domeny, więc cała "praca budowania" jest wykonywana w warstwie pośredniej (klasie) mapper, która jest i DomainObjects. Więc to jest najlepsze lub ogólnie zalecane podejście do tej sprawy? Proszę tylko o upewnienie się, że punkt został zrozumiany.
Victor,
4
Tak, warstwa nazywa się „Assembler”. Używając trzeciej warstwy do zdefiniowania mapowań, dajesz możliwość łatwej zamiany warstwy asemblera na inną implementację (np .: usunięcie Automappera i użycie ręcznych mapowań), co jest zawsze lepszym wyborem. Najlepszym sposobem, aby to zrozumieć, jest zastanowienie się, gdzie dałbym ci Obiekt A, a ktoś inny dał ci Obiekt B. Nie masz dostępu do każdego z tych obiektów (tylko dll), więc mapowanie można wykonać tylko poprzez utworzenie trzeciego warstwa. Ale nawet jeśli masz dostęp do któregokolwiek z obiektów, mapowania należy zawsze wykonywać na zewnątrz, ponieważ nie są one powiązane.
Frederik Prijck
1
Ale ta odpowiedź jest w rzeczywistości "bardziej niż użyteczna" z komentarzami i poprawkami, przynosi każdemu czytelnikowi potwierdzenie i wskazówki dotyczące problemu .. naprawdę pomaga w nauce, nie rozumiem, dlaczego nie głosować .. pomaga mi .. ale nie chcę rozpoczynać dyskusji na ten temat .. do ciebie. W każdym razie dzięki za odpowiedź.
Victor,
3
Właściwie podoba mi się to podejście, obecnie używam konstruktora do mapowania jednostki na DTO i używam klasy mappera do mapowania wejścia dto na jednostkę.
dream83619
1

Inne możliwe rozwiązanie: http://glue.codeplex.com .

Funkcje:

  • Dwukierunkowe mapowanie
  • Automatyczne mapowanie
  • Mapowanie między różnymi typami
  • Mapowanie zagnieżdżone i spłaszczanie
  • Listy i tablice
  • Weryfikacja relacji
  • Testowanie mapowania
  • Właściwości, pola i metody
Saly
źródło
0

Mogę zasugerować narzędzie, które stworzyłem i jest open source hostowane w CodePlex: EntitiesToDTOs .

Mapowanie z DTO do Entity i odwrotnie jest realizowane za pomocą metod rozszerzających, które tworzą stronę asemblera na każdym końcu.

Kończysz kodem takim jak:

Foo entity = new Foo();
FooDTO dto = entity.ToDTO();
entity = dto.ToEntity();

List<Foo> entityList = new List<Foo>();
List<FooDTO> dtoList = entityList.ToDTOs();
entityList = dtoList.ToEntities();
kzfabi
źródło
jest to błąd architektoniczny, ponieważ uświadamiasz sobie nawzajem DTO i podmioty domeny.
Raffaeu,
5
@Raffaeu Nie sądzę, ponieważ metody ToDTO / ToDTOs / ToEntity / ToEntities są zdefiniowane jako metody rozszerzające, które reprezentują asemblerów. Logika konwertowania jednostki na DTO i odwrotnie znajduje się w metodach rozszerzających (asemblerach), a nie w jednostce / DTO.
kzfabi
2
Jeśli mówisz o „asemblerze”, zaimplementuj je we właściwy sposób. Uczyń je modułowymi, spraw, by można je było łatwo wymieniać, użyj wstrzykiwania zależności. Nie ma potrzeby, aby sam model domeny był świadomy konwersji na DTO. Powiedzmy, że mam 1 obiekt domeny, ale 50 różnych aplikacji używających tej samej domeny, z których każda ma swoje własne DTO. Nie masz zamiaru tworzyć 50 rozszerzeń. Zamiast tego utworzysz jedną usługę aplikacji dla każdej aplikacji z niezbędnymi asemblerami, które zostaną wstrzyknięte jako zależność do usługi.
Frederik Prijck
0

Dlaczego nie możemy tego zrobić?

class UserDTO {
}

class AdminDTO {
}

class DomainObject {

 // attributes
 public DomainObject(DTO dto) {
      this.dto = dto;
 }     

 // methods
 public function isActive() {
      return (this.dto.getStatus() == 'ACTIVE')
 }

 public function isModeratorAdmin() {
      return (this.dto.getAdminRole() == 'moderator')
 }

}


userdto = new UserDTO();
userdto.setStatus('ACTIVE');

obj = new DomainObject(userdto)
if(obj.isActive()) {
   //print active
}

admindto = new AdminDTO();
admindto.setAdminRole('moderator');

obj = new DomainObject(admindto)
if(obj.isModeratorAdmin()) {
   //print some thing
}

@FrederikPrijck (lub) ktoś: Proszę zasugerować. W powyższym przykładzie DomainObject jest zależny od DTO. W ten sposób mogę uniknąć kodowania mapowania obiektu domeny <--> dto.

lub DomainObject może rozszerzyć klasę DTO?

user3767551
źródło
0

Inną opcją byłoby użycie ModelProjector . Obsługuje wszystkie możliwe scenariusze i jest bardzo łatwy w użyciu przy minimalnej powierzchni.

user10269
źródło
0

Możemy do tego użyć wzorca Factory, Memento i Builder. Fabryka ukrywa szczegóły dotyczące tworzenia wystąpienia modelu domeny z DTO. Memento zajmie się serializacją / deserializacją modelu domeny do / z DTO, a nawet może uzyskać dostęp do prywatnych członków. Builder pozwoli na mapowanie z DTO do domeny z płynnym interfejsem.

Irwansyah
źródło