Всякий раз, когда разработчик спрашивает «в чем смысл делать это?», Они на самом деле имеют в виду «я не вижу ни одного варианта использования, когда это дает преимущество». Для этого позвольте мне показать вам несколько примеров.
Все примеры будут основаны на этой простой модели данных:
У Person
объекта есть пять свойств:Id, FirstName, LastName, Age, CityId
И вы можете предположить, что приложение использует эти данные различными способами (отчеты, формы, всплывающие окна, ...).
Все приложение уже существует. Все, что я упоминаю, является изменением существующей кодовой базы. Это важно помнить.
Пример 1 - Изменение базовой структуры данных - без DTO
Требования изменились. Возраст человека должен быть динамически извлечен из правительственной базы данных (предположим, основываясь на его имени и фамилии).
Поскольку вам больше не нужно хранить Age
значение локально, его необходимо удалить из Person
сущности. Здесь важно понимать, что сущность представляет данные базы данных , и ничего более. Если это не в базе данных, это не в сущности.
Когда вы извлекаете возраст из правительственного веб-сервиса, он будет храниться в другом объекте (или int).
Но ваш интерфейс все еще отображает возраст. Все представления были настроены для использования Person.Age
свойства, которое больше не существует. Проблема представляет собой: все взгляды, которые относятся к Age
человеку, должны быть исправлены .
Пример 2 - Изменение базовой структуры данных - с DTO
В старой системе, есть также PersonDTO
объект с теми же пятью свойствами: Id, FirstName, LastName, Age, CityId
. После получения a Person
, сервисный уровень преобразует его в a PersonDTO
и затем возвращает его.
Но теперь требования изменились. Возраст человека должен быть динамически извлечен из правительственной базы данных (предположим, основываясь на его имени и фамилии).
Поскольку вам больше не нужно хранить Age
значение локально, его необходимо удалить из Person
сущности. Здесь важно понимать, что сущность представляет данные базы данных , и ничего более. Если это не в базе данных, это не в сущности.
Однако, поскольку у вас есть посредник PersonDTO
, важно видеть , что этот класс может держать в Age
собственности. Сервисный уровень извлекает Person
, преобразует его в a PersonDTO
, затем он также извлекает возраст человека из правительственного веб-сервиса, сохраняет это значение PersonDTO.Age
и передает этот объект.
Важной частью здесь является то, что любой, кто использует уровень обслуживания, не видит разницы между старой и новой системой . Это включает в себя ваш интерфейс. В старой системе он получил полный PersonDTO
объект. И в новой системе он по-прежнему получает полный PersonDTO
объект. Представления не должны быть обновлены .
Вот что мы имеем в виду, когда используем фразу разделения интересов : есть две разные проблемы (хранение данных в базе данных, представление данных во внешнем интерфейсе), и каждый из них нуждается в различном типе данных. Даже если эти два типа данных сейчас содержат одни и те же данные, это может измениться в будущем.
В данном примере Age
есть различие между двумя типами данных: Person
(объект базы данных) не нуждается Age
, но PersonDTO
(тип данных внешнего интерфейса) действительно нуждается в этом.
Отделяя задачи (= создавая отдельные типы данных) с самого начала, кодовая база намного более устойчива к изменениям, внесенным в модель данных.
Вы можете утверждать, что наличие объекта DTO при добавлении нового столбца в базу данных означает, что вы должны выполнить двойную работу, добавив свойство как в сущности, так и в DTO. Это технически правильно. Требуется немного дополнительных усилий, чтобы поддерживать два класса вместо одного.
Тем не менее, вам нужно сравнить усилия, необходимые. При добавлении одного или нескольких новых столбцов копирование / вставка нескольких свойств занимает не так много времени. Когда модель данных изменяется структурно, для изменения внешнего интерфейса, возможно, таким образом, что это вызывает ошибки только во время выполнения (а не во время компиляции), требуется значительно больше усилий, и это требует от разработчика (-ов) поиска ошибок.
Я мог бы привести вам больше примеров, но принцип всегда будет таким же.
Подвести итоги
- Отдельные обязанности (проблемы) должны работать отдельно друг от друга. Они не должны совместно использовать какие-либо ресурсы, такие как классы данных (например
Person
)
- Тот факт, что сущность и ее DTO имеют одинаковые свойства, не означает, что вам нужно объединить их в одну сущность. Не срезайте углы.
- В качестве более наглядного примера, скажем, наша база данных содержит страны, песни и людей. Все эти объекты имеют
Name
. Но только то, что они имеют Name
свойство, не означает, что мы должны заставить их наследовать от общего EntityWithName
базового класса. Различные Name
свойства не имеют никакого значимого отношения.
- Если одно из свойств когда-либо изменится (например, песня
Name
будет переименована Title
, или человек получит « FirstName
а» LastName
), им придется потратить больше усилий, чтобы отменить наследство, которое вам даже не нужно .
- Хотя это не так очевидно, ваш аргумент, что вам не нужен DTO, когда у вас есть сущность, тот же. Вы смотрите на сейчас , но вы не готовитесь к будущим изменениям. ЕСЛИ сущность и DTO абсолютно одинаковы, и ЕСЛИ вы можете гарантировать, что никогда не произойдет никаких изменений в модели данных; тогда вы правы, что можете опустить DTO. Но дело в том, что вы никогда не сможете гарантировать, что модель данных никогда не изменится.
- Хорошая практика не всегда окупается сразу. Это может начать окупаться в будущем, когда вам нужно будет вернуться к старому приложению.
- Основной убийцей существующих кодовых баз является ухудшение качества кода, что постоянно усложняет поддержание кодовой базы, пока не превратится в бесполезный беспорядок кода спагетти, который не поддерживается.
- Надлежащая практика, такая как реализация разделения проблем от начала работы, направлена на то, чтобы избежать этого скользкого пути плохого обслуживания, чтобы поддерживать кодовую базу как можно дольше поддерживаемой.
Как правило, при рассмотрении разделения проблем думайте об этом следующим образом:
Предположим, что каждая проблема (пользовательский интерфейс, база данных, логика) обрабатывается другим человеком в другом месте. Они могут общаться только по электронной почте.
В хорошо разделенной кодовой базе изменение конкретной задачи должно обрабатываться только одним человеком:
- Изменение пользовательского интерфейса требует только пользовательского интерфейса.
- Изменение метода хранения данных требует только разработчика базы данных.
- Изменение бизнес-логики включает только бизнес-разработчика.
Если бы все эти разработчики использовали одну Person
и ту же сущность и в нее были внесены незначительные изменения, все должны были бы участвовать в этом процессе.
Но используя отдельные классы данных для каждого слоя, эта проблема не так распространена:
- Пока разработчик базы данных может возвращать действительный
PersonDTO
объект, бизнес и пользовательский интерфейс не заботятся о том, что он изменил способ хранения / извлечения данных.
- Пока бизнес-разработчик хранит данные в базе данных и предоставляет необходимые данные внешнему интерфейсу, разработчикам базы данных и пользовательского интерфейса все равно, решил ли он переделать свои бизнес-правила.
- Пока пользовательский интерфейс может быть разработан на основе PersonViewModel, пользовательский интерфейс может создавать пользовательский интерфейс так, как он хочет. Разработчикам баз данных и бизнесу все равно, как это делается, поскольку это не влияет на них.
Ключевая фраза здесь, так как это не влияет на них . Реализация правильного разделения интересов направлена на то, чтобы минимизировать влияние (и, следовательно, необходимость вовлечения) других сторон.
Конечно, нельзя избежать некоторых серьезных изменений, включая более одного человека, например, когда в базу данных добавляется совершенно новый объект. Но не стоит недооценивать количество незначительных изменений, которые вы должны сделать за время жизни приложения. Основные изменения в численном меньшинстве.
What's the benefit of these conversions?
отделение модели постоянства данных от модели данных (представления), предлагаемой потребителям. Преимущества развязки широко обсуждались в SE. Однако цель DTO - собрать в одном ответе столько информации, сколько необходимо клиентам для сохранения вызовов на сервере. Что делает общение клиент-сервер более гладким.