Чи повинні служби завжди повертати DTO, чи вони також можуть повертати доменні моделі?


174

Я (пере) проектую масштабне додаток, ми використовуємо багатошарову архітектуру на основі DDD.

У нас є MVC з рівнем даних (реалізація сховищ), доменним рівнем (визначення доменної моделі та інтерфейсів - сховища, служби, блок роботи), сервісний рівень (реалізація послуг). Поки ми використовуємо доменні моделі (в основному сутності) на всіх шарах, і ми використовуємо DTO лише як моделі перегляду (в контролері, сервісі повертаються доменні моделі (і), а контролер створює модель перегляду, яка передається в представлення).

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

Питання полягає в тому, що - якщо ми суворо користуємося переглядаючими моделями, чи нормально повертати доменні моделі аж до контролерів, чи завжди ми повинні використовувати DTO для спілкування з рівнем обслуговування? Якщо так, то чи добре налаштовувати доменні моделі на основі того, які послуги потрібні? (Чесно кажучи, я не вважаю це так, оскільки служби повинні споживати те, що має домен.) Якщо ми повинні чітко дотримуватися DTO, чи повинні вони визначатися в рівні обслуговування? (Я думаю, що так.) Іноді зрозуміло, що ми повинні використовувати DTO (наприклад, коли сервіс виконує багато ділової логіки та створює нові об'єкти), іноді зрозуміло, що ми повинні використовувати тільки доменні моделі (наприклад, коли служба Членство повертає анемічного користувача ( s) - мабуть, не було б особливого сенсу створювати DTO, який є таким же, як доменна модель) - але я віддаю перевагу послідовності та належній практиці.

Article Domain vs DTO vs ViewModel - Як і коли ними користуватися? (а також деякі інші статті) дуже схожа на мою проблему, але це не відповідає на це питання. Стаття Чи слід реалізувати DTO в шаблоні сховищ з EF? також подібний, але він не має відношення до DDD.

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

Як завжди, дякую.


28
Для тих хлопців, які голосують за близьку, будь ласка, ви б хотіли пояснити, чому ви хочете закрити це питання на основі думки?
Роберт Голдвейн

20
@Aron "Перегляд коду - це питання з питаннями та відповідями для спільного використання коду з проектів, над якими ви працюєте для експертної оцінки". - моє запитання зовсім не стосується коду, тому це було б поза темою; ТАК: "Зосередьтеся на питаннях про актуальну проблему, з якою ви зіткнулися. Включіть деталі про те, що ви намагалися та що саме ви намагаєтеся зробити". - У мене є конкретна експертна проблема, яку я намагався вирішити. Не могли б ви бути більш конкретними, що не так у цьому питанні, оскільки багато питань стосуються архітектури і такі питання, мабуть, нормально, тож я можу уникнути будь-яких подальших непорозумінь?
Роберт Голдвайн

7
Дякую, що задали це запитання. Ви зробили мені послугу і зробили моє життя набагато простішим та щасливішим, дякую.
Лоа

9
@RobertGoldwein, не заважай ТАК Близька мафія, ваше питання легітимне.
hyankov

3
Велике спасибі за те, що ви
задали

Відповіді:


177

це не вірно, коли модель домену залишає бізнес-рівень (рівень обслуговування)

Відчуваєш, що ти витягуєш кишки прямо? За словами Мартіна Фаулера: Сервісний шар визначає обсяг програми, вона інкапсулює домен. Іншими словами, він захищає домен.

Іноді сервісу потрібно повернути об’єкт даних, який не був визначений у домені

Чи можете ви навести приклад цього об’єкта даних?

Якщо ми повинні чітко дотримуватися DTO, чи повинні вони визначатися в рівні обслуговування?

Так, оскільки відповідь є частиною вашого сервісного рівня. Якщо він визначений "десь інше", то в сервісному шарі потрібно посилатися на "десь ще", додаючи новий шар до вашої лазаньї.

це нормально повертати доменні моделі аж до контролерів, або ми завжди повинні використовувати DTO для спілкування з рівнем обслуговування?

DTO є об'єктом відповіді / запиту, це має сенс, якщо ви використовуєте його для спілкування. Якщо ви використовуєте моделі домену у вашому шарі презентації (MVC-Controllers / View, WebForms, ConsoleApp), то презентаційний шар щільно пов'язаний з вашим доменом, будь-які зміни в домені вимагають від вас змінити контролери.

здається, не було б багато сенсу створювати DTO, який є таким же, як модель домену)

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

DTO може додати додаткові складності вашому застосуванню, але так само ваші шари. DTO - дорога функція вашої системи, вони не виходять безкоштовними.

Навіщо використовувати DTO

Ця стаття надає як перевагу, так і недолік використання DTO, http://guntherpopp.blogspot.com/2010/09/to-dto-or-not-to-dto.html

Підсумок:

Коли використовувати

  • Для великих проектів.
  • Термін експлуатації проекту - 10 років і вище.
  • Стратегічне, критичне застосування місії.
  • Великі команди (більше 5)
  • Розробники розподіляються географічно.
  • Домен та презентація різні.
  • Скорочення обміну накладними даними (початкове призначення DTO)

Коли не використовувати

  • Проект від малого до середнього розміру (максимум 5 членів)
  • Термін експлуатації проекту становить 2 роки.
  • Немає окремої команди для GUI, бекенда тощо.

Аргументи проти DTO

Аргументи з DTO

  • Без DTO презентація та домен тісно поєднані. (Це нормально для малих проектів.)
  • Стабільність інтерфейсу / API
  • Може забезпечити оптимізацію шару презентації, повернувши DTO, що містить лише ті атрибути, які абсолютно необхідні. Використовуючи linq-проекцію , вам не доведеться тягнути цілу сутність.
  • Щоб зменшити вартість розробки, використовуйте інструменти для генерації коду

3
Привіт, дякую за вашу відповідь, це справді хороший підсумок, а також дякую за посилання. Моє речення "Іноді сервісу потрібно повернути об'єкт даних, який не був визначений у домені" було обрано погано, це означає, що служба поєднує кілька DO з одного сховища (наприклад, атрибутів) і виробляє один POCO як композицію цих атрибутів (на основі з бізнес-логіки). Ще раз дякую за дійсно приємну відповідь.
Роберт Голдвейн

1
Важливим питанням ефективності є саме те, як ці доменні моделі або DTO повертаються з вашої служби. Якщо ви реалізуєте запит на повернення конкретної колекції моделей домену (наприклад, з .ToArray () або ToList ()), ви вибираєте всі стовпці для заселення реалізованих об'єктів. Якщо натомість запроектувати DTO у запиті, EF доцільно вибрати лише стовпці, необхідні для заповнення вашої DTO, що може значно менше даних для передачі в деяких випадках.
фронт

10
Ви можете зіставити об’єкти "вручну". Я знаю, нудні речі, але це займає 2-3 хвилини на кожну модель, і завжди є можливість принести багато проблем, коли ви використовуєте багато роздумів (AutoMapper тощо)
Razvan Dumitru

1
дякую, що відповіли так просто і з таким змістом. Ви зробили мені послугу і зробили моє життя набагато простішим та щасливішим, дякую.
Лоа

1
Ми мали 10 мільйонів проектів скасувати, тому що це було повільно ... Чому це було повільно? Перенесення об’єкта DTO по всьому місцю за допомогою рефлекції. Будь обережний. Automapper також використовує відображення.
RayLoveless

11

Здається, що ваш додаток є великим і складним, оскільки ви вирішили пройти метод DDD. Не повертайте ваші суб'єкти poco або так звані доменні об'єкти та об'єкти цінності на рівні обслуговування. Якщо ви хочете це зробити, тоді видаліть службовий рівень, тому що він вам більше не потрібен! Об'єкти перегляду моделі або передачі даних повинні жити на рівні сервісу, оскільки вони повинні відображати членів моделі домену та навпаки. То навіщо вам мати DTO? У складному застосуванні з великою кількістю сценаріїв вам слід розділити питання щодо домену та представлення представлень, модель домену може бути розділена на кілька DTO, а також кілька моделей домену можуть бути згорнуті в DTO. Тож краще створити свій DTO у багатошаровій архітектурі, навіть якщо це буде те саме, що і ваша модель.

Чи повинні ми завжди використовувати DTO для спілкування з рівнем обслуговування? Так, вам потрібно повернути DTO за своїм службовим рівнем, оскільки ви спілкуєтесь зі своїм сховищем у службовому рівні з членами доменної моделі та відображаєте їх у DTO та повертаєтесь до контролера MVC і навпаки.

Чи добре налаштовувати доменні моделі залежно від необхідних послуг? Служба просто розмовляє з методами сховища та домену та доменними послугами. Ви повинні вирішити бізнес у своєму домені виходячи з ваших потреб, і це не сервісне завдання сказати домену, що потрібно.

Якщо ми повинні чітко дотримуватися DTO, чи повинні вони визначатися в рівні обслуговування? Так, спробуйте мати DTO або ViewModel просто на сервісі пізніше, тому що їх слід перенести на доменних членів на рівні обслуговування, і це не дуже гарна ідея розміщувати DTO в контролерах вашої програми (спробуйте використовувати шаблон запиту відповіді у вашому сервісному шарі), ура !


1
Вибач за це! ви можете побачити це тут ehsanghanbari.com/blog/Post/7/…
Ehsan

10

На мій досвід, ви повинні робити те, що практично. «Найкращий дизайн - це найпростіший дизайн, який працює» - Ейнштейн. З цим розумом ...

якщо ми суворо використовуємо моделі перегляду, чи нормально повертати доменні моделі аж до контролерів, чи завжди ми повинні використовувати DTO для зв'язку з рівнем обслуговування?

Абсолютно це нормально! Якщо у вас є доменні об'єкти, моделі DTO та View, то включаючи таблиці баз даних, усі поля в додатку повторюються в 4 місцях. Я працював над великими проектами, де Доменні об'єкти та Моделі перегляду працювали чудово. Єдине розширення для цього полягає в тому, якщо додаток поширюється і сервісний рівень знаходиться на іншому сервері, і в цьому випадку DTO повинні надсилати через провід з причин серіалізації.

Якщо так, то чи добре налаштовувати доменні моделі на основі того, які послуги потрібні? (Чесно кажучи, я не думаю, оскільки служби повинні споживати те, що має домен.)

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

Якщо ми повинні чітко дотримуватися DTO, чи повинні вони визначатися в рівні обслуговування? (Я думаю так.)

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

Удачі!


8

Я запізнююсь на цю партію, але це настільки поширене і важливе питання, на яке я відчував вимушеність відповісти.

Під "послугами" ви маєте на увазі "Шар програми", описаний Еваном у блакитній книзі ? Я припускаю, що ви це робите, і в такому випадку відповідь полягає в тому, що вони не повинні повертати DTO. Я пропоную прочитати розділ 4 у синій книзі під назвою "Ізоляція домену".

У цій главі Еванс говорить про шари наступне:

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

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

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

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

Наприклад, що робити, якщо ваша система взаємодіє з декількома іншими системами або типами клієнтів, кожна з яких потребує власного DTO? Звідки ви знаєте, до якого DTO повинен повернутися метод вашої служби додатків? Як би ви вирішили цю проблему, якщо ваша обрана мова не дозволяє перевантажувати метод (метод обслуговування в даному випадку) на основі типу повернення? І навіть якщо ви знайдете спосіб, навіщо порушувати ваш додаток, щоб підтримувати проблему шару презентації?

На практиці це крок вниз по дорозі, яка закінчиться архітектурою спагетті. Я бачив подібну деволюцію та її результати на власному досвіді.

Де я зараз працюю, сервіси в нашому шарі додатків повертають об’єкти домену. Ми не вважаємо це проблемою, оскільки рівень інтерфейсу (тобто інтерфейсу / презентації) залежить від рівня домену, який знаходиться під ним. Крім того, ця залежність зводиться до типу "лише посилання", оскільки:

a) Інтерфейсний шар може отримувати доступ до цих об’єктів Домену лише як значення, що повертаються лише для читання, отримані викликами до рівня програми

b) методи служб на рівні додатків приймають як вхід лише "необроблений" вхід (значення даних) або параметри об'єкта (для зменшення кількості параметрів, де це необхідно), визначених у цьому рівні. Зокрема, сервіси додатків ніколи не приймають об’єкти Домену як вхідні дані.

Інтерфейсний шар використовує методи відображення, визначені в самому шарі інтерфейсу, для відображення з об’єктів Домену в DTO. Знову ж таки, це тримає DTO орієнтованими на адаптерах, які управляються інтерфейсним шаром.


1
Швидке запитання. Наразі я спінінг на те, що повернути зі свого шару програми. Повернення сутностей домену з шару програми відчуває себе неправильно. Чи справді я хочу просочити домен "зовні"? Тож я розглядав DTO з додаткового шару. Але це додає іншу модель. У своїй відповіді ви сказали, що ви повертаєте доменні моделі як "значення, що повертаються лише для читання". Як ти це робиш? Тобто, як ви змушуєте їх читати лише?
Майкл Ендрюс

Я думаю, що я просто прийму вашу позицію. Прикладні служби повертають моделі доменів. Шари адаптера портів (REST, презентація тощо) потім перекладають їх у власну модель (переглядають модель або уявлення). Додавання моделі DTO між додатком та адаптерами портів здається непосильним. Повернення моделей домену все ще дотримується DIP, і логіка домену залишається всередині обмеженого контексту (не обов'язково всередині межі програми. Але це здається прекрасним компромісом).
Майкл Ендрюс

@MichaelAndrews, рада почути, що моя відповідь допомогла. Re: Ваше запитання про повернені об'єкти, які читаються лише самими об'єктами, насправді не є лише для читання (тобто незмінними). Що я маю на увазі, що це не відбувається на практиці (принаймні, з мого досвіду). Для оновлення доменного об'єкта шару інтерфейсу необхідно було б: а) посилатися на сховище об’єкта домену, або б) передзвонити в рівень додатка для оновлення щойно отриманого об'єкта. Будь-яке з них є настільки явними порушеннями належної практики DDD, що я вважаю, що вони повинні виконувати себе. Не соромтеся відповісти на цю відповідь, якщо вам це потрібно.
BitMask777

Ця відповідь дуже інтуїтивна для мене з кількох причин. По-перше, ми можемо повторно використовувати один і той же рівень програми для декількох інтерфейсів (API, контролери), і кожен може перетворити модель так, як вважає за потрібне. По-друге, якби ми трансформували модель в DTO в додатку. Шар, це означатиме, що DTO визначено в додатку. Шар, що в свою чергу означає, що DTO зараз є частиною нашого обмеженого контексту (не обов'язково домену!) - це просто відчуває себе неправильно.
Роботрон

1
Я щойно збирався задати вам подальше запитання, а потім побачив, що ви вже відповіли на нього: "Служби прикладних програм ніколи не приймають об'єкти Домену як вхідні дані". Я б ще +1, якби міг.
Роботрон

5

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

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


1
Через роки ми дійшли такого ж висновку, саме з причин, про які ви згадали тут.
Роберт Голдвайн

@RobertGoldwein Це чудово! Зараз я відчуваю себе впевненіше у своєму рішенні. :-)
Ніклас Вульф

@NiklasWulff: таким чином, про які йдеться, Dtos тепер є частиною договору про рівень додатків, тобто є частиною core / domain. Як щодо типів повернення Web API ? Чи виставляєте ви вказані у вашій відповіді дані, або ви виділили Моделі перегляду, визначені в шарі Web API? Або, інакше кажучи: ви призначаєте Dtos для перегляду моделей?
Robotron

1
@Robotron У нас немає веб-API, ми використовуємо MVC. Так що так, ми мапимо dto: s на різні моделі перегляду. Часто модель перегляду містить безліч інших речей для відображення веб-сторінки, тому дані з dto: s складають лише частину моделі перегляду
Niklas Wulff

4

Поки ми використовуємо доменні моделі (в основному сутності) на всіх рівнях, і ми використовуємо DTO лише як моделі перегляду (у контролері, сервісі повертаються доменні моделі (і), а контролер створює модель перегляду, яка передається в представлення).

Оскільки Доменна модель надає термінологію ( всюдисущу мову ) для вашої програми, то краще використовувати Доменну модель широко.

Єдина причина використовувати ViewModels / DTO - це реалізація шаблону MVC у вашій програмі для розділення View(будь-якого шару презентації) та Model(Доменної моделі). У цьому випадку ваша презентація та модель домену слабко пов'язані.

Іноді сервісу потрібно повернути об’єкт даних, який не був визначений у домені, і тоді нам або потрібно додати новий об’єкт до домену, який не відображається, або створити об’єкт POCO (це некрасиво, оскільки деякі служби повертають моделі домену, деякі ефективно повернути DTO).

Я припускаю, що ви говорите про послуги Application / Business / Logic Logic.

Я пропоную вам повернути об’єкти домену, коли зможете. Якщо потрібно повернути додаткову інформацію, допустимо повернути DTO, який містить кілька об'єктів домену.

Іноді люди, які використовують рамки 3-ї частини, які генерують проксі-сервіси над доменними об'єктами, стикаються з труднощами піддавати доменним об’єктам їхні послуги, але це лише питання неправильного використання.

Питання полягає в тому, що - якщо ми суворо користуємося переглядаючими моделями, чи нормально повертати доменні моделі аж до контролерів, чи завжди ми повинні використовувати DTO для спілкування з рівнем обслуговування?

Я б сказав, що достатньо повернути об'єкти домену у 99,9% випадків.

Для того, щоб спростити створення DTO і відображення в них доменних об'єктів, ви можете використовувати AutoMapper .


4

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

Дуже важливим аспектом доменної моделі є те, що її легко змінити. Це робить нас гнучкими до змін вимог домену.


2

Я б запропонував проаналізувати ці два питання:

  1. Чи споживають ваші верхні шари (тобто перегляд та перегляд моделей / контролерів) по-різному, ніж піддається шару домену? Якщо робиться багато картування або навіть задіяна логіка, я пропоную переглянути ваш дизайн: це, мабуть, має бути ближче до того, як фактично використовуються дані.

  2. Наскільки ймовірно, що ви глибоко змінюєте верхні шари? (наприклад, заміна ASP.NET на WPF). Якщо це дуже не відрізняється, а ваша архітектура не дуже складна, вам може бути краще виставити якомога більше об'єктів домену.

Боюся, це досить широка тема, і вона дійсно зводиться до того, наскільки складна ваша система та її вимоги.


У нашому випадку верхній шар точно не зміниться. У деяких випадках служба повертає досить унікальний об'єкт POCO (побудований з більшості доменів - наприклад, користувача та файлів, якими він володіє), у деяких випадках служба повертає просто доменну модель - наприклад, результат "FindUserByEmail () повинен повернути модель домену користувача - і ось моє занепокоєння, іноді наша служба (и) повертає доменну модель, іноді нову DTO - і мені не подобається ця невідповідність, я читаю стільки статей, скільки могло, і більшість, здається, згодні з тим, що хоча карта Доменної моделі <-> DTO дорівнює 1: 1, модель домену не повинна залишати сервісний рівень - тому я розірваний
Роберт Голдвейн

У такому сценарії та за умови, що ви можете нести додаткові зусилля щодо розробки, я б також пішов із картою, щоб ваш рівень шару був більш послідовним.
jnovo

1

На мій досвід, якщо ви не використовуєте шаблон інтерфейсу OO (як голі об'єкти), піддавати об’єкти домену користувальницькому інтерфейсу - це погана ідея. Це тому, що в міру зростання програми додаються потреби користувальницького інтерфейсу і змушують ваші об'єкти вміщувати ці зміни. Ви в кінцевому підсумку обслуговуєте двох майстрів: UI та DOMAIN, що дуже болісно. Повірте, ти не хочеш бути там. Модель користувальницького інтерфейсу має функцію спілкування з користувачем, модель DOMAIN для дотримання бізнес-правил, а моделі стійкості стосуються ефективного зберігання даних. Всі вони відповідають різним потребам програми. Я в середині запису про це в блозі, додаю його, коли це буде зроблено.

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