У нас есть веб-API ASP.NET, который предоставляет REST API для нашего одностраничного приложения. Мы используем DTO / POCO для передачи данных через этот API.
Проблема в том, что эти DTO со временем становятся больше, поэтому мы хотим реорганизовать DTO.
Я ищу "лучшие практики", как проектировать DTO: В настоящее время у нас есть небольшие DTO, которые состоят только из полей типа значения, например:
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Другие DTO используют это UserDto по составу, например:
public class TaskDto
{
public int Id { get; set; }
public UserDto AssignedTo { get; set; }
}
Кроме того, существуют некоторые расширенные DTO, которые определяются путем наследования от других, например:
public class TaskDetailDto : TaskDto
{
// some more fields
}
Поскольку некоторые DTO использовались для нескольких конечных точек / методов (например, GET и PUT), они постепенно расширялись некоторыми полями с течением времени. И благодаря наследованию и составу другие DTO также стали больше.
Мой вопрос: наследование и состав не являются хорошими практиками? Но когда мы не используем их повторно, создается впечатление, что мы пишем один и тот же код несколько раз. Является ли плохой практикой использование DTO для нескольких конечных точек / методов, или должны быть разные DTO, которые отличаются только некоторыми нюансами?
Ответы:
В качестве лучшей практики постарайтесь сделать свои DTO максимально лаконичными. Верните только то, что вам нужно вернуть. Используйте только то, что вам нужно. Если это означает несколько дополнительных DTO, пусть будет так.
В вашем примере задача содержит пользователя. Вероятно, там не нужен полный пользовательский объект, может быть, просто имя пользователя, которому назначена задача. Нам не нужны остальные пользовательские свойства.
Допустим, вы хотите переназначить задачу другому пользователю, у вас может возникнуть желание передать полный объект пользователя и полный объект задачи во время повторного назначения. Но что действительно нужно, так это просто идентификатор задачи и идентификатор пользователя. Это только две части информации, необходимые для переназначения задачи, поэтому смоделируйте DTO как таковое. Обычно это означает, что есть отдельный DTO для каждого повторного вызова, если вы стремитесь к худой модели DTO.
Также иногда может понадобиться наследование / композиция. Допустим, у кого-то есть работа. Работа имеет несколько задач. В этом случае получение работы также может вернуть список заданий для работы. Таким образом, нет никаких правил против композиции. Это зависит от того, что моделируется.
источник
Если ваша система не основана исключительно на операциях CRUD, ваши DTO слишком детализированы. Попробуйте создать конечные точки, которые воплощают бизнес-процессы или артефакты. Этот подход хорошо соотносится со слоем бизнес-логики и с «доменным дизайном» Эрика Эванса.
Например, допустим, у вас есть конечная точка, которая возвращает данные для счета-фактуры, чтобы они могли отображаться на экране или в форме для конечного пользователя. В модели CRUD вам потребуется несколько вызовов к вашим конечным точкам, чтобы собрать необходимую информацию: имя, платежный адрес, адрес доставки, позиции. В контексте бизнес-транзакции один DTO из одной конечной точки может вернуть всю эту информацию за один раз.
источник
Трудно найти лучшие практики для чего-то столь же «гибкого» или абстрактного, как DTO. По сути, DTO являются только объектами для передачи данных, но в зависимости от пункта назначения или причины передачи вы можете применить различные «лучшие практики».
Я рекомендую прочитать « Шаблоны архитектуры корпоративных приложений» Мартина Фаулера . Есть целая глава, посвященная шаблонам, где DTO получают действительно подробный раздел.
Первоначально они были «разработаны» для использования в дорогих удаленных вызовах, где вам, вероятно, понадобится много данных из разных частей вашей логики; DTO будут осуществлять передачу данных за один вызов.
По словам автора, DTO не были предназначены для использования в локальной среде, но некоторые люди нашли для них применение. Обычно они используются для сбора информации из разных POCO в единую сущность для GUI, API или разных уровней.
Теперь, с наследованием, повторное использование кода больше похоже на побочный эффект наследования, чем на его основную цель; Композиция, с другой стороны, реализована с повторным использованием кода в качестве основной цели.
Некоторые люди рекомендуют использовать композицию и наследование вместе, используя сильные стороны обоих и пытаясь смягчить свои слабости. Следующее является частью моего умственного процесса при выборе или создании новых DTO или любого нового класса / объекта в этом отношении:
Некоторые из них, возможно, не «лучшие» практики, они работают достаточно хорошо для проектов, над которыми я работал, но вы должны помнить, что ни один размер не подходит всем. В случае универсального DTO вы должны быть осторожны, мои методы подписи выглядят так:
Если какой-либо из методов когда-либо нуждается в своем собственном DTO, я делаю наследование, и обычно единственное изменение, которое мне нужно сделать, - это параметр, хотя иногда мне нужно копать глубже для конкретных случаев.
Из ваших комментариев я понимаю, что вы используете вложенные DTO. Если ваши вложенные DTO состоят только из списка других DTO, я думаю, что лучше всего развернуть список.
В зависимости от объема данных, которые вам нужно отображать или работать с ними, может быть неплохо создать новые DTO, которые ограничивают данные; например, если в вашем UserDTO много полей и вам нужны только 1 или 2, может быть лучше иметь DTO только с этими полями. Определение уровня, контекста, использования и полезности DTO очень поможет при его разработке.
источник
Использование композиции в DTO - прекрасная практика.
Наследование среди конкретных типов DTO - плохая практика.
С одной стороны, в языке, подобном C #, автоматически реализуемое свойство имеет очень мало накладных расходов на сопровождение, поэтому их дублирование (я действительно ненавижу дублирование) не так вредно, как это часто бывает.
Одна из причин не использовать конкретное наследование среди DTO состоит в том, что некоторые инструменты будут с радостью отображать их в неправильных типах.
Например, если вы используете утилиту базы данных, такую как Dapper (я не рекомендую ее, но она популярна), которая выполняет
class name -> table name
вывод, вы можете легко в конечном итоге сохранить производный тип как конкретный базовый тип где-то в иерархии и тем самым потерять данные или, что еще хуже ,Более глубокая причина не использовать наследование среди DTO заключается в том, что его не следует использовать для совместного использования реализаций между типами, которые не имеют очевидных отношений "является". На мой взгляд,
TaskDetail
не похоже на подтипTask
. С таким же успехом это может быть свойствоTask
или, что еще хуже, супертипTask
.Теперь одна вещь, которая вас может беспокоить, - это поддержание согласованности между именами и типами свойств различных связанных DTO.
В то время как наследование конкретных типов, как следствие, поможет обеспечить такую согласованность, гораздо лучше использовать интерфейсы (или чисто виртуальные базовые классы в C ++) для поддержания такой согласованности.
Рассмотрим следующие DTO
источник