Должны ли службы всегда возвращать DTO или модели доменов?

175

Я (пере) проектирую крупномасштабное приложение, мы используем многоуровневую архитектуру на основе DDD.

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

Я читал бесчисленные статьи об использовании, не использовании, отображении и передаче DTO. Я понимаю, что нет однозначного ответа, но я не уверен, нормально ли это, или не возвращает модели доменов из сервисов в контроллеры. Если я возвращаю модель предметной области, она все равно никогда не передается в представление, поскольку контроллер всегда создает модель представления, зависящую от вида, - в этом случае это кажется допустимым. С другой стороны, он не чувствует себя хорошо, когда доменная модель покидает бизнес-уровень (сервисный уровень). Иногда службе необходимо вернуть объект данных, который не был определен в домене, и тогда нам нужно либо добавить новый объект в домен, который не отображается, либо создать объект POCO (это ужасно, поскольку некоторые службы возвращают модели домена, некоторые эффективно вернуть DTOs).

Вопрос заключается в том, что если мы строго используем модели представлений, можно ли возвращать модели доменов вплоть до контроллеров, или мы всегда должны использовать DTO для связи с уровнем обслуживания? Если да, то можно ли корректировать доменные модели в зависимости от того, какие услуги нужны? (Честно говоря, я так не думаю, поскольку сервисы должны потреблять то, что имеет домен.) Если мы должны строго придерживаться DTO, должны ли они быть определены на уровне сервисов? (Я так думаю.) Иногда ясно, что мы должны использовать DTO (например, когда служба выполняет много бизнес-логики и создает новые объекты), иногда ясно, что мы должны использовать только модели домена (например, когда служба членства возвращает анемичного пользователя ( s) - кажется, не имеет смысла создавать DTO, аналогичный модели предметной области), - но я предпочитаю последовательность и хорошие практики.

Статья Домен против DTO против ViewModel - Как и когда их использовать? (а также некоторые другие статьи) очень похожа на мою проблему, но не отвечает на этот вопрос (ы). Статья Должен ли я реализовать DTO в репозитории с EF? также похож, но это не касается DDD.

Отказ от ответственности: я не собираюсь использовать какой-либо шаблон проектирования только потому, что он существует и причудливый, с другой стороны, я хотел бы использовать хорошие шаблоны проектирования и практики также потому, что это помогает проектировать приложение в целом, помогает с разделением проблем, даже если использование определенного шаблона не является «необходимым», по крайней мере, на данный момент.

Как всегда, спасибо.

Роберт Голдвейн
источник
28
Для тех парней, которые голосуют за близких - не могли бы вы объяснить, почему вы хотите закрыть этот вопрос как основанный на мнении?
Роберт Голдвейн
20
@Aron «Code Review - это сайт вопросов и ответов для обмена кодом из проектов, над которыми вы работаете, для экспертной оценки». - мой вопрос совсем не о коде, так что это было бы не по теме; SO: «Сосредоточьтесь на вопросах о реальной проблеме, с которой вы столкнулись. Включите подробную информацию о том, что вы пробовали и что именно вы пытаетесь сделать». - У меня есть конкретная экспертная проблема, которую я пытался решить. Не могли бы вы быть более конкретным, что не так с этим вопросом, так как многие вопросы здесь касаются архитектуры, и такие вопросы, по-видимому, в порядке, поэтому я могу избежать дальнейших недоразумений?
Роберт Голдвейн
7
Спасибо, что задали этот вопрос. Вы сделали мне одолжение и сделали мою жизнь намного проще и счастливее, спасибо.
Лоа
9
@RobertGoldwein, не обращайте внимания на ТАКУЮ Близкую Мафию, ваш вопрос правомерен.
Гяньков
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-проекцию , вам не нужно тянуть всю сущность.
  • Чтобы снизить стоимость разработки, используйте инструменты генерации кода
Yorro
источник
3
Привет, спасибо за ваш ответ, это действительно хорошее резюме, а также спасибо за ссылки. Мое предложение «Иногда службе нужно возвращать объект данных, который не был определен в домене», было выбрано неправильно, это означает, что служба объединяет несколько DO из одного репозитория (например, атрибутов) и создает один POCO как композицию этих атрибутов (на основе по бизнес-логике). Еще раз спасибо за действительно хороший ответ.
Роберт Голдвейн
1
Важным фактором производительности является то, как эти доменные модели или DTO возвращаются из вашего сервиса. В EF, если вы реализуете запрос на возврат конкретной коллекции моделей предметной области (например, с помощью .ToArray () или ToList ()), вы выбираете все столбцы для заполнения реализованных объектов. Если вместо этого вы проецируете DTO в запросе, EF разумно выберет только те столбцы, которые необходимы для заполнения DTO, а в некоторых случаях может быть значительно меньше данных для передачи.
фыркнул
10
Вы можете нанести на карту ваши объекты «от руки». Я знаю, скучная вещь, но занимает 2-3 минуты на модель, и всегда есть возможность доставить много проблем, когда вы используете много отражений (AutoMapper и т. Д.)
Разван Думитру
1
спасибо за ответ так просто и с таким содержанием. Вы сделали мне одолжение и сделали мою жизнь намного проще и счастливее, спасибо.
Лоа
1
У нас был отменен проект на 10 миллионов, потому что он был медленным ... Почему он был медленным? Передача объекта DTO повсеместно с использованием референции. Быть осторожен. Automapper также использует отражение.
RayLoveless
11

Кажется, что ваше приложение достаточно большое и сложное, поскольку вы решили использовать подход DDD. Не возвращайте ваши poco-сущности или так называемые доменные сущности и объекты-значения в слое вашего сервиса. Если вы хотите сделать это, то удалите слой обслуживания, потому что он вам больше не нужен! Объекты View Model или Data Transfer должны находиться в слое Service, поскольку они должны отображаться на членов модели домена и наоборот. Так зачем вам нужен DTO? В сложном приложении с большим количеством сценариев вы должны разделить интересы домена и представления презентации, модель домена может быть разделена на несколько DTO, а несколько моделей домена могут быть объединены в DTO. Поэтому лучше создать DTO в многоуровневой архитектуре, даже если она будет такой же, как ваша модель.

Должны ли мы всегда использовать DTO для связи с уровнем обслуживания? Да, вы должны возвращать DTO по уровню обслуживания, так как вы общаетесь со своим репозиторием на уровне обслуживания с членами модели домена, сопоставляете их с DTO и возвращаетесь к контроллеру MVC и наоборот.

Можно ли корректировать доменные модели в зависимости от того, какие услуги нужны? Служба просто взаимодействует с методами репозитория и домена и доменными службами, вы должны решать бизнес в своем домене исходя из своих потребностей, и задача службы не состоит в том, чтобы сообщить домену, что нужно.

Если мы должны строго придерживаться DTO, должны ли они быть определены на уровне обслуживания?Да, попробуйте позже использовать DTO или ViewModel только потому, что они должны быть сопоставлены с членами домена на сервисном уровне, и не стоит помещать DTO в контроллеры вашего приложения (попробуйте использовать шаблон запроса ответа на сервисном уровне), ура !

Ehsan
источник
1
Извини за это! Вы можете увидеть это здесь ehsanghanbari.com/blog/Post/7/…
Ehsan
10

По моему опыту вы должны делать то, что практично. «Лучший дизайн - это самый простой дизайн, который работает», - Эйнштейн. С этим разум ...

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

Абсолютно все нормально! Если у вас есть доменные сущности, DTO и модели просмотра, то, включая таблицы базы данных, все поля в приложении повторяются в 4 местах. Я работал над большими проектами, в которых доменные сущности и модели просмотра работали просто отлично. Единственное исключение - если приложение распределено, а уровень обслуживания находится на другом сервере, и в этом случае DTO необходимо отправлять по проводам по причинам сериализации.

Если да, то можно ли корректировать доменные модели в зависимости от того, какие услуги нужны? (Честно говоря, я так не думаю, поскольку сервисы должны потреблять то, что имеет домен.)

Обычно я бы согласился и сказал бы «нет», потому что модель предметной области, как правило, отражает бизнес-логику и обычно не формируется потребителем этой логики.

Если мы должны строго придерживаться DTO, должны ли они быть определены на уровне обслуживания? (Я думаю так.)

Если вы решите использовать их, я соглашусь и скажу «да», уровень обслуживания - идеальное место, так как он возвращает DTO в конце дня.

Удачи!

Джастин Рикеттс
источник
8

Я опаздываю на эту вечеринку, но это такой распространенный и важный вопрос, на который я был вынужден ответить.

Под «услугами» вы подразумеваете «уровень приложений», описанный Эваном в синей книге ? Я предполагаю, что вы делаете, и в этом случае ответ заключается в том, что они не должны возвращать DTO. Я предлагаю прочитать главу 4 в синей книге под названием «Изоляция домена».

В этой главе Эванс говорит о слоях следующее:

Разбейте сложную программу на слои. Разрабатывайте дизайн внутри каждого слоя, который является связным и зависит только от слоев ниже.

Для этого есть веская причина. Если вы используете понятие частичного порядка в качестве меры сложности программного обеспечения то наличие уровня в зависимости от уровня выше повышает сложность, что снижает удобство обслуживания.

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

Если ваш прикладной уровень зависит от этих DTO, он зависит от уровня над собой, и ваша сложность возрастает. Я могу гарантировать, что это увеличит сложность обслуживания вашего программного обеспечения.

Например, что если ваша система взаимодействует с несколькими другими системами или типами клиентов, для каждого из которых требуется собственный DTO? Откуда вы знаете, какой DTO метод вашего приложения должен вернуть? Как бы вы решили эту проблему, если ваш язык не позволяет перегрузить метод (в данном случае, метод службы) на основе типа возвращаемого значения? И даже если вы придумали способ, зачем нарушать ваш прикладной уровень для поддержки проблемы уровня презентации?

В практическом плане это шаг по пути, который закончится архитектурой спагетти. Я видел такого рода деволюцию и ее результаты на собственном опыте.

Там, где я сейчас работаю, сервисы нашего уровня приложений возвращают доменные объекты. Мы не считаем это проблемой, поскольку уровень интерфейса (т.е. пользовательского интерфейса / представления) зависит от уровня домена, который находится ниже. ним. Кроме того, эта зависимость сводится к типу зависимости «только для ссылки», потому что:

a) Интерфейсный уровень может получить доступ только к этим объектам домена как возвращаемые значения только для чтения, полученные при обращении к прикладному уровню

b) методы служб на прикладном уровне принимают в качестве входных данных только «необработанные» входные данные (значения данных) или параметры объекта (для уменьшения количества параметров при необходимости), определенные на этом уровне. В частности, сервисы приложений никогда принимают объекты домена в качестве входных данных.

Интерфейсный уровень использует методы отображения, определенные в самом Интерфейсном уровне, для отображения из объектов Домена в DTO. Опять же, это позволяет DTO сосредоточиться на том, чтобы быть адаптерами, которые контролируются интерфейсным уровнем.

BitMask777
источник
1
Быстрый вопрос. В настоящее время я вращаюсь над тем, что вернуть со своего уровня приложения. Возврат доменных сущностей из прикладного уровня кажется неправильным. Действительно ли я хочу просочиться в домен "наружу"? Так что я размышлял о DTO на уровне приложений. Но это добавляет другую модель. В своем ответе вы сказали, что возвращаете доменные модели как «возвращаемые значения только для чтения». Как ты это делаешь? Т.е. как вы заставляете их читать только?
Майкл Эндрюс
Я думаю, что я просто собираюсь принять вашу позицию. Службы приложений возвращают доменные модели. Затем слои адаптера порта (REST, презентация и т. Д.) Преобразуют их в свою собственную модель (модель представления или представления). Добавление модели DTO между приложением и адаптерами портов кажется излишним. Возвращающиеся доменные модели по-прежнему придерживаются DIP, и логика домена остается в ограниченном контексте (не обязательно внутри границы приложения. Но это кажется хорошим компромиссом).
Майкл Эндрюс
@MichaelAndrews, рад слышать, что мой ответ помог. Re: ваш вопрос о том, что возвращаемые объекты доступны только для чтения, сами объекты не являются действительно доступными только для чтения (т.е. неизменяемыми). Я имею в виду, что это не происходит на практике (по крайней мере, по моему опыту). Чтобы обновить объект домена, Интерфейсный уровень должен был бы либо a) ссылаться на хранилище объекта домена, либо b) вызвать обратно на прикладной уровень, чтобы обновить объект, который он только что получил. Любое из них является настолько явным нарушением надлежащей практики DDD, что я считаю, что они принуждаются самостоятельно. Не стесняйтесь высказать ответ, если хотите.
BitMask777
Этот ответ очень интуитивен для меня по нескольким причинам. Во-первых, мы можем повторно использовать один и тот же прикладной уровень для нескольких пользовательских интерфейсов (API, контроллеров), и каждый из них может трансформировать модель так, как считает нужным. Во-вторых, если бы мы преобразовали модель в DTO в приложении. Слой, который будет означать DTO, определен в приложении. Слой, который, в свою очередь, означает, что DTO теперь является частью нашего ограниченного контекста (не обязательно домена!) - это просто неправильно.
Роботрон
1
Я как раз собирался задать вам дополнительный вопрос, а затем увидел, что вы уже ответили на него: «сервисы приложений никогда не принимают объекты домена в качестве входных данных». Я бы снова +1, если бы мог.
Robotron
5

Поздно к вечеринке, но я сталкиваюсь с точно такой же архитектурой и склоняюсь к «только DTO от обслуживания». Это главным образом потому, что я решил использовать только доменные объекты / агрегаты для поддержания валидности внутри объекта, то есть только при обновлении, создании или удалении. Когда мы запрашиваем данные, мы используем только EF в качестве хранилища и отображаем результат в DTO. Это позволяет нам оптимизировать запросы на чтение и не адаптировать их к бизнес-объектам, часто используя функции базы данных, поскольку они быстры.

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

Никлас Вульф
источник
1
Спустя годы мы пришли к такому же выводу, именно по причинам, которые вы здесь указали.
Роберт Голдвейн
@RobertGoldwein Это здорово! Теперь я чувствую себя более уверенно в своем решении. :-)
Никлас Вульф
@NiklasWulff: так что рассматриваемые Dtos теперь являются частью контракта прикладного уровня, то есть являются частью ядра / домена. Как насчет типов возврата Web API ? Предоставляете ли вы Dtos, упомянутые в вашем ответе, или у вас есть выделенные модели просмотра, определенные в слое Web API? Или, говоря по-другому: вы отображаете Dtos для просмотра моделей?
Роботрон
1
@Robotron У нас нет Web API, мы используем MVC. Так что да, мы сопоставляем dto: s с различными моделями представления. Часто модель представления содержит много других материалов для отображения веб-страницы, поэтому данные из dto: s составляют только часть модели представления
Никлас Вульф
4

До сих пор мы использовали доменные модели (в основном сущности) на всех уровнях и использовали DTO только в качестве моделей представления (в контроллере сервис возвращает модель (ы) домена, а контроллер создает модель представления, которая передается представлению).

Поскольку Доменная Модель предоставляет терминологию ( Ubiquitous Language ) для всего вашего приложения, лучше широко использовать Доменную Модель.

Единственная причина использования ViewModels / DTO - это реализация шаблона MVC в вашем приложении для разделенияView (любого уровня представления) и Model(модели предметной области). В этом случае ваша презентация и модель предметной области слабо связаны.

Иногда службе необходимо вернуть объект данных, который не был определен в домене, и тогда нам нужно либо добавить новый объект в домен, который не отображается, либо создать объект POCO (это ужасно, поскольку некоторые службы возвращают модели домена, некоторые эффективно вернуть DTOs).

Я предполагаю, что вы говорите об услугах приложений / бизнеса / доменной логики.

Я предлагаю вам вернуть доменные объекты, когда вы можете. Если необходимо вернуть дополнительную информацию, допустимо вернуть DTO, который содержит несколько сущностей домена.

Иногда люди, которые используют платформы 3-й части, которые генерируют прокси для сущностей домена, сталкиваются с трудностями, открывая доменные сущности из своих сервисов, но это только вопрос неправильного использования.

Вопрос заключается в том, что если мы строго используем модели представлений, можно ли возвращать модели доменов вплоть до контроллеров, или мы всегда должны использовать DTO для связи с уровнем обслуживания?

Я бы сказал, что достаточно вернуть доменные объекты в 99,9% случаев.

Чтобы упростить создание DTO и сопоставление сущностей вашего домена в них, вы можете использовать AutoMapper .

Илья Палкин
источник
4

Если вы вернете часть своей доменной модели, она станет частью контракта. Контракт трудно изменить, так как от него зависят вещи вне вашего контекста. Таким образом, вам будет сложно изменить часть вашей доменной модели.

Очень важным аспектом доменной модели является то, что ее легко изменить. Это делает нас гибкими к изменяющимся требованиям домена.

Timo
источник
2

Я бы предложил проанализировать эти два вопроса:

  1. Ваши верхние уровни (т.е. модели просмотра / просмотра / контроллеры) потребляют данные другим способом, чем тот, который предоставляет уровень домена? Если выполняется много картирования или даже логики, я предлагаю пересмотреть ваш дизайн: вероятно, он должен быть ближе к тому, как на самом деле используются данные.

  2. Насколько вероятно, что вы глубоко измените свои верхние слои? (например, замена ASP.NET для WPF). Если это очень непохоже, а ваша архитектура не очень сложна, возможно, вам лучше разоблачить как можно больше доменных сущностей.

Боюсь, что это довольно широкая тема, и она действительно сводится к тому, насколько сложна ваша система и каковы ее требования.

jnovo
источник
В нашем случае верхний слой наверняка не изменится. В некоторых случаях служба возвращает довольно уникальный объект POCO (созданный из нескольких доменов - например, пользователя и файлов, которыми он владеет), в некоторых случаях служба возвращает просто модель домена - например, результат «FindUserByEmail () должен возвращать модель домена пользователя - и вот что меня беспокоит, иногда наши сервисы возвращают модель предметной области, иногда новую DTO - и мне не нравится это несоответствие, я прочитал столько статей, сколько смог, и большинство, похоже, согласны с тем, что хотя модель сопоставления доменов <-> DTO - 1: 1, модель домена не должна покидать уровень обслуживания - поэтому я разорван
Роберт Голдвейн
В таком сценарии и при условии, что вы можете нести дополнительные усилия по разработке, я бы также пошел с отображением, чтобы ваши слои были более последовательными
'17
1

По моему опыту, если вы не используете шаблон OO UI (например, голые объекты), подвергать доменные объекты пользовательскому интерфейсу - плохая идея. Это связано с тем, что по мере роста приложения потребности пользовательского интерфейса меняются и вынуждают ваши объекты учитывать эти изменения. Вы заканчиваете тем, что служили 2 мастерам: UI и DOMAIN, что очень болезненно. Поверь мне, ты не хочешь быть там. Модель пользовательского интерфейса имеет функцию связи с пользователем, модель DOMAIN для хранения бизнес-правил, а модели постоянства - для эффективного хранения данных. Все они направлены на разные потребности приложения. Я нахожусь в середине записи в блоге об этом, добавлю это, когда это будет сделано.

max_cervantes
источник