Найкращі практики зіставлення DTO з об’єктом домену?


83

Я бачив багато запитань, пов’язаних із зіставленням DTO до об’єктів домену, але я не відчував, що вони відповіли на моє запитання. Я вже використовував багато методів і маю власну думку, але шукаю щось більш конкретне.

Ситуація:

У нас багато об’єктів домену. Ми використовуємо модель CSLA, тому наші об’єкти домену можуть бути досить складними і вони містять власний доступ до даних. Ви не хочете передавати їх по дроту. Ми будемо писати деякі нові сервіси, які повертатимуть дані у різних форматах (.Net, JSON тощо). З цієї причини (та з інших причин) ми також створюємо м'який об'єкт передачі даних, який буде передаватися по дроту.

Моє запитання: Як об'єднати об'єкт DTO та Domain?

Моя перша реакція полягає у використанні рішення Фаулера типу DTO . Я бачив це багато разів, і це мені здається правильним. Об'єкт домену не містить посилання на DTO. Зовнішня сутність ("mapper" або "асемблер") викликається для створення DTO з об'єкта домену. Зазвичай на стороні об'єкта домену є ORM . Недоліком цього є те, що "картограф", як правило, стає надзвичайно складним для будь-якої реальної ситуації і може бути дуже крихким.

Інша ідея, що висувається, полягає в тому, щоб об'єкт домену "містив" DTO, оскільки це просто постний об'єкт даних. Властивості об’єкта домену внутрішньо посилаються на властивості DTO і можуть просто повернути DTO, якщо про це попросять. Я не бачу проблем із цим, але відчуваю себе неправильно. Я бачив деякі статті, де люди, які використовують NHibernate, використовували цей метод.

Чи є інші шляхи? Чи варто використовувати один із наведених вище способів? Якщо так чи ні, чому?


4
Автомат виглядає цікаво. Я вже бачив багато коду, який би замінив. Моє головне питання полягає в тому, що якщо я з якої-небудь причини застряю в тонні картографічного коду, я вважаю за краще мати контроль над ним сам.
Брайан Елліс,

2
Коли ми переходимо від DTO до Domain Objects, це відображення є 100% ручним. Цю проблему вирішити набагато складніше, оскільки ми намагаємось зберегти об’єкти домену на основі операцій, а не просто контейнери даних. Перехід до DTO - це проблема, яку легко вирішити.
Jimmy Bogard

Інший варіант - це бета-версія ServiceToolkit.NET, яку ми запустили під час нашого останнього проекту. Можливо, це може вам допомогти: http://servicetoolkit.codeplex.com/

Я б погодився, що це неправильно, якщо об'єкт домену не повинен знати про об'єкт dto. Хоча в цьому випадку вони можуть бути пов’язані між собою, їх призначення є абсолютно окремим (dtos, як правило, створюються спеціально), і ви створили б непотрібну залежність.
Sinaesthetic

Відповіді:


40

Перевага наявності маппера, який знаходиться між вашим доменом і вашим DTO, не така помітна, коли ви підтримуєте лише одне зіставлення, але зі збільшенням кількості зіставлення, якщо цей код ізольований від домену, допомагає зробити домен простішим та вигіднішим. Ви не будете захаращувати свій домен великою вагою.

Особисто я намагаюся уникати відображення моїх сутностей домену і покладаю відповідальність на те, що я називаю "Рівень менеджера / служби". Це шар, який знаходиться між додатком та репозиторієм (ями) та забезпечує ділову логіку, таку як координація робочого циклу (якщо ви модифікуєте A, можливо, вам доведеться змінити B, тому служба A працюватиме зі службою B).

Якби у мене було багато можливих форматів закінчень, я міг би поглянути на створення модуля форматування, який міг би використовувати шаблон Visitor, наприклад, для перетворення моїх сутностей, але я ще не знайшов потреби ні в чому такому складному.


"(Якщо ви модифікуєте A, можливо, вам доведеться також змінити B, щоб служба A працювала зі службою B)" - Чи це не підпадає під ділову логіку? Я думаю, що ця частина повинна переходити до контролера, а не до служби?
Ayyappa

24

Ви можете використовувати автоматичний пристрій, такий як написаний Джиммі Богардом, який не має зв’язку між об’єктами і покладається на дотримувані правила іменування.


9
Automapper може призвести до випадково виявлених властивостей -> отвір безпеки. Краще було б прямо сказати, що повинно бути викрито як DTO.
Дімон

4
@deamon: поважне занепокоєння, але так само є і помилки (і потенційні діри в безпеці через людський нагляд), які можна створити, написавши весь той неприємний картографічний код. Я піду на автоматичну дорогу і оброблю 5%, використовуючи вбудовану спеціальну функцію картографування.
Merritt

@deamon - ви не можете просто зробити умовне відображення для тих властивостей, які ви не повинні виставляти? Думаючи, що AutoMapper обробляє цей сценарій?
Richard B

Якщо ви використовуєте AutoMapper, я вважаю надзвичайно важливим, щоб у вас були всі модульні тести, щоб перевірити, чи правильно виконано відображення.
L-Four

8

Зберігання логіки відображення всередині вашої сутності означає, що ваш доменний об'єкт тепер знає "деталь реалізації", про яку йому не потрібно знати. Як правило, DTO - це ваш шлюз у зовнішній світ (або з вхідного запиту, або через зчитування із зовнішньої служби / бази даних). Оскільки сутність є частиною вашої бізнес-логіки, можливо, найкраще зберігати ці деталі поза організацією.

Ведення картографування де-небудь ще було б єдиною альтернативою - але куди воно має йти? Я спробував представити об'єкти / послуги для картографування, але, після того, як все було сказано і зроблено, це здавалося надмірним проектуванням (і, мабуть, було). Я мав певний успіх, використовуючи Automapper і подібні для невеликих проектів, але такі інструменти, як Automapper, мають свої власні підводні камені. У мене було досить важко знайти проблеми, пов’язані із зіставленнями, тому що зіставлення Automapper є неявними та повністю відокремленими від решти вашого коду (не як "розділення проблем", а більше як "де живе богозабуте картографування"), тому вони іноді важко відстежити. Не кажучи вже про те, що Automapper не використовує, тому що він має. Я просто думаю, що картографування має бути якомога очевиднішим та прозорішим, щоб уникнути проблем.

Замість того, щоб створювати рівень служби картографування, я мав великий успіх, зберігаючи свої відображення всередині своїх DTO. Оскільки DTO завжди сидять на межі програми, їх можна поінформувати про бізнес-об'єкт і зрозуміти, як зіставити з них / з ними. Навіть коли кількість відображень масштабується до розумної величини, це працює чисто. Всі зіставлення знаходяться в одному місці, і вам не потрібно керувати набором служб відображення всередині вашого рівня даних, рівня антикорупції або рівня презентації. Натомість відображення - це лише деталь реалізації, делегована DTO, яка бере участь у запиті / відповіді. Оскільки серіалізатори зазвичай серіалізують властивості та поля, коли ви надсилаєте їх по дроту, у вас не повинно виникати жодних проблем. Особисто я знайшов це найчистіший варіант і, на своєму досвіді, можу сказати,


7

Ми використовуємо шаблони T4 для створення класів зіставлення.

Pro - зручний для читання код, доступний під час компіляції, швидше, ніж середовище виконання. 100% контроль над кодом (можна використовувати часткові методи / шаблон шаблону для розширення функціональних можливостей на спеціальній основі)

Con - виключаючи певні властивості, колекції об’єктів домену тощо, вивчення синтаксису T4.


3

Як ви бачите реалізацію конструктора всередині класу DTO, який приймає за параметр об'єкт домену?

Скажіть ... Щось подібне

class DTO {

     // attributes 

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

     // methods
}

10
Будь ласка, ніколи цього не роби. Ви не хочете, щоб ваш рівень DTO знав про рівень вашого домену або залежав від нього. Перевага відображення полягає в тому, що нижчі шари можуть бути легко вимкнені, змінивши відображення, або що модифікації нижнього шару можуть бути контролерами, змінивши відображення. Скажімо, dtoA сьогодні відображається в domainObjectA, але завтра вимога полягає в тому, що він відображається у domainObjectB. У вашому випадку вам доведеться змінити об'єкт DTO, що є великим ні-ні. Ви втратили багато переваг картографа.
Frederik Prijck

2
По-перше, дякую! : D. Отже, @FrederikPrijck, вставляючи шар між DTOі DomainObject, ми в основному прагнемо, щоб ця проблема DTO залежала від об'єкта домену, тому всі "будівельні роботи" виконуються у середньому рівні (класі) mapper, який називається , що залежить від обох DTO та DomainObjects. То що найкраще, або взагалі рекомендувати, підходити до цього питання? Я лише прошу переконатись, що суть зрозуміла.
Віктор

4
Так, шар називається "Асемблер". Використовуючи 3-й шар для визначення зіставлення, ви надаєте можливість легко замінити шар асемблера іншою реалізацією (наприклад: видалити Automapper та використовувати ручні зіставлення), що завжди є кращим вибором. Найкращий спосіб зрозуміти це - придумати, куди я дав би вам об’єкт A, а хтось інший - об’єкт B. У вас немає доступу до кожного з цих об’єктів (лише dll), тож картографування можна зробити лише шляхом створення 3 шар. Але навіть якщо ви можете отримати доступ до будь-якого з об'єктів, відображення завжди слід робити зовні, оскільки вони не пов'язані між собою.
Фредерік Прийк,

1
Але ця відповідь насправді "більш ніж корисна" з коментарями та виправленнями, вона приносить будь-якому читачеві визнання та поради щодо проблеми .. вона справді сприяє навчанню, я не розумію, чому голосувати. Вона мені допомагає .. але я не хочу розпочинати дискусію про це .. до вас. У будь-якому разі дякую за відповідь.
Віктор

3
Насправді мені подобається такий підхід, в даний час я використовую конструктор для зіставлення сутності з DTO і використовую клас mapper для відображення вводу dto в сутність.
dream83619

1

Інше можливе рішення: http://glue.codeplex.com .

Особливості:

  • Двонаправлене відображення
  • Автоматичне відображення
  • Картування між різними типами
  • Вкладене відображення та сплощення
  • Списки та масиви
  • Перевірка відносин
  • Тестування відображення
  • Властивості, поля та методи


0

Я можу запропонувати створений мною інструмент із відкритим кодом, розміщений на CodePlex: EntitiesToDTOs .

Перетворення від DTO до Entity і навпаки реалізується методами розширення, вони складають сторону Асемблера кожного кінця.

Ви закінчуєте таким кодом:

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();

це архітектурно неправильно, оскільки ви усвідомлюєте DTO та сутності домену один одного.
Раффо

5
@Raffaeu Я не думаю, оскільки методи ToDTO / ToDTOs / ToEntity / ToEntities визначаються як методи розширення, що представляє Асемблерів. Логіка перетворення сутності в DTO і навпаки полягає в методах розширення (Асемблери), а не в сутності / DTO насправді.
kzfabi

2
Якщо ви говорите про "Асемблер", то реалізуйте їх правильно. Зробіть їх модульними, зробіть їх зручними для заміни, використовуйте ін’єкцію залежностей. Немає необхідності, щоб сама модель домену знала про перехід на DTO. Скажімо, у мене є 1 об’єкт домену, але 50 різних додатків, що використовують один і той же домен, кожен з яких має свій власний DTO. Ви не збираєтеся створювати 50 розширень. Натомість ви створите одну службу додатків для кожної програми, а необхідний асемблер (и) вводиться як залежність до служби.
Frederik Prijck

0

Чому б нам не зробити так?

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 (або) хтось: Будь ласка, підкажіть . У наведеному вище прикладі DomainObject залежить від DTO. Таким чином, я можу уникнути коду для відображення dto <--> domainobject.

або клас DomainObject може розширити клас DTO?


0

Іншим варіантом буде використання ModelProjector . Він підтримує всі можливі сценарії і дуже простий у використанні з мінімальними розмірами.


0

Для цього ми можемо використовувати шаблон Factory, Memento та Builder. Фабрика приховує деталі щодо створення екземпляра моделі домену з DTO. Memento подбає про серіалізацію / десеріалізацію моделі домену в / з DTO і може навіть отримати доступ до приватних членів. Builder will дозволяє зіставляти з DTO на домен з вільним інтерфейсом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.