Dto что это такое

DTO vs POCO vs Value Object

Определения DTO, POCO и Value Object

Вначале небольшая ремарка по поводу Value Object. В C# существует похожая концепция, называемая Value Type. Это всего лишь деталь имплементации того, как объекты хранятся в памяти и мы не будем касаться этого. Value Object, о котором пойдет речь, — понятие из среды DDD (Domain-Driven Design).

Ок, давайте начнем. Вы возможно заметили, что такие понятия как DTO, Value Object и POCO часто используются как синонимы. Но действительно ли они означают одно и то же?

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

С другой стороны, Value Object — это полноценный член вашей доменной модели. Он подчиняется тем же правилам, что и сущности (Entities). Единственное отличие между Value Object и Entity в том, что у Value Object-а нет собственной идентичности. Это означает, что два Value Object-а с одинаковыми свойствами могут считаться идентичными, в то время как две сущности отличаются друг от друга даже в случае если их свойства полностью совпадают.

Value Object-ы могут содержать логику и обычно они не используются для передачи информации между приложениями.

POJO был представлен Мартином Фаулером в качестве альтернативы для JavaBeans и других «тяжелых» enterprise-конструкций, которые были популярны в ранних 2000-х.

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

Другой хороший пример анти-POCO подхода — Entity Framework до версии 4.0. Каждый класс, сгенерированный EF, наследовал от EntityObject, что привносило в домен логику, специфичную для EF. Начиная с версии 4, Entity Framework добавил возможность работать с POCO моделью — возможность использовать классы, которые не наследуются от EntityObject.

Таким образом, понятие POCO означает использование настолько простых классов насколько возможно для моделирования предметной области. Это понятие помогает придерживаться принципов YAGNI, KISS и остальных best practices. POCO классы могут содержать логику.

Корреляция между понятиями

Есть ли связи между этими тремя понятиями? В первую очередь, DTO и Value Object отражают разные концепции и не могут использоваться взаимозаменяемо. С другой стороны, POCO — это надмножество для DTO и Value Object:

Dto что это такое. Смотреть фото Dto что это такое. Смотреть картинку Dto что это такое. Картинка про Dto что это такое. Фото Dto что это такое

Другими словами, Value Object и DTO не наследуют никаким сторонним компонентам и таким образом являются POCO. В то же время, POCO — это более широкое понятие: это может быть Value Object, Entity, DTO или любой другой класс в том случае если он не наследует компонентам, не относящимся напрямую к решаемой вами проблеме.

Вот свойства каждого из них:

Dto что это такое. Смотреть фото Dto что это такое. Смотреть картинку Dto что это такое. Картинка про Dto что это такое. Фото Dto что это такое

Заметьте, что POCO-класс может и иметь, и не иметь собственной идентичности, т.к. он может быть как Value Object, так и Entity. Также, POCO может содержать, а может и не содержать логику внутри себя. Это зависит от того, является ли POCO DTO.

Заключение

Вышесказанное в статье можно суммировать следующим образом:

Источник

Чистим пхпшный код с помощью DTO

Это моя первая статья, так что ловить камни приготовился.

Возможно, такой подход в PHP сложился исторически, из-за отсутствия строгой типизации и такого себе ООП. Ведь как по мне, то только с 7 версии можно было более-менее реализовать типизацию+ООП, используя strict_types и type hinting.

Также вызов подобных методов может сопровождаться описанием массива, который мы будем передавать. Или вовсе передается какой-то массив с мусором, а метод просто берет нужные ему ключи. Например, сервис по созданию пользователя:

Меня такой подход не устраивает, поэтому я уже долгое время придерживаюсь DTO’шек. Мы создаем нужный нам DTO объект, который требует метод, и его уже передаем. И тогда внутри метода мы с полной уверенностью можем работать с валидными данными. Мы точно знаем, что у него есть нужные типизированные параметры, нет ничего лишнего, а конечный метод у нас становится гораздо чище.

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

Собственно, так и появился мой пакет.

Использование ClassTransformer

С его помощью я автоматически привожу данные к нужному мне классу. Рассмотрим более подробно все также на примере создание пользователя. К нам приходит запрос, и данные из него мы должны отправить в метод. В моем случае на Laravel проекте это выглядит вот так:

В запросе к нам приходит массив параметров: name, phone и email. Пакет просто смотрит есть ли такие параметры у класса, и, если есть, сохраняет значение. В противном случае просто отсеивает их. На входе transform можно передавать не только массив, это может быть другой object, из которого также будут разобраны нужные параметры.

Но наименования аргументов могут отличаться. Тогда, в созданной нами DTO, мы можем спокойно описать свою реализацию приведения:

Существуют объекты гораздо сложнее, с параметрами определенного класса, либо массивом объектов. Что же с ними? Все просто, указываем параметру в PHPDoc путь к классу и все. В случае массива нужно указать, каких именно объектов этот массив:

Указывая параметру класс, вы можете строить более сложные цепочки связей. Пакет рекурсивно будет проходить по всем типизированным параметрам и приведет их к нужному формату.

Путь к классу обязательно должен быть полным, т.к. иначе гораздо сложнее реализовать нахождение класса для инициализации. Или это вообще будет alias и нужно будет парсить файл, а это уже будет оверхед для такого сервиса.

Метод сервиса работает с конкретным набором данным

Знаем все параметры, которые есть у объекта

Можно задать типизацию каждому параметру

Вызов метода становится проще, за счет удаления приведения вручную

В IDE работают все подсказки.

Аналоги

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

Roadmap

Реализовать метод afterTransform, который будет вызываться после инициализации DTO. Это позволит более гибко кастомизировать приведение к классу. В данный момент, если входные ключи отличаются от внутренних DTO, нужно самому описывать метод transform. И если у нас из 20 параметров только у одного отличается ключ, нам придется описать приведение всех 20. А с методом afterTransform мы сможем кастомизировать приведение только нужного нам параметра, а все остальные обработает пакет.

Источник

DTO в JS

Информационные системы предназначены для обработки данных, а DTO (Data Transfer Object) является важным концептом в современной разработке. В “классическом” понимании DTO являются простыми объектами (без логики), описывающими структуры данных, передаваемых “по проводам” между разнесенными процессами (remote processes). Зачастую данные «по проводам» передаются в виде JSON.

Если DTO используются для передачи данных между слоями приложения (база данных, бизнес-логика, представления), то, по Фаулеру, это называется LocalDTO. Некоторые разработчики (включая самого Фаулера) негативно относятся к локальным DTO. Основным отрицательным моментом локального применения DTO является необходимость маппинга данных из одной структуры в другую при их передаче от одного слоя приложения к другому.

Тем не менее, DTO являются важным классом объектов в приложениях и в этой статье я покажу JS-код, который на данный момент считаю оптимальным для DTO (в рамках стандартов ECMAScript 2015+).

Структура данных

Во-первых, в коде должна быть отражена сама структура данных. Лучше всего это делать с использованием классов (аннотации JSDoc помогают ориентироваться в типах данных):

Это пример простой структуры данных, где каждый атрибут является примитивом. Если некоторые атрибуты сами являются структурами, то класс выглядит примерно так:

Создание объектов

Как правило, создание экземпляра DTO в половине случаев связано разбором имеющейся структуры данных, полученной «по проводам» с «другой стороны». Поэтому конструктор DTO получает на вход некоторый JS-объект, из которого пытается извлечь знакомые ему данные:

В конструкторе структуры со сложными атрибутами используются конструкторы для соответствующих атрибутов:

Если какой-то атрибут представляет из себя массив, то в конструкторе его разбор выглядит примерно так:

Если какие-то данные должны быть сохранены в атрибуте без разбора, то это тоже возможно (хотя к DTO имеет такое себе отношение):

Метаданные

Например, при выборке данных из БД:

Резюме

Если сводить воедино все три составляющих DTO (структура, конструктор, метаданные), то получается примерно такой es-модуль:

Подобный подход позволяет извлекать знакомые подструктуры данных из больших структур (конфигурационных файлов, ответов от сервисов и т.п.), непосредственно относящиеся к текущему контексту, а IDE, за счёт аннотаций, имеет возможность помогать разработчику ориентироваться в этой подструктуре.

Послесловие

Что привлекло внимание. Во-первых, двухступенчатое создание объекта:

Во-вторых, значения по-умолчанию вешаются на прототип, используемый для создания экземпляров класса:

Это опять-таки направлено на минимизацию потребления памяти при массовом создании экземпляров.

В третьих, отсутствуют метаданные об используемых в DTO атрибутах:

Наверное, с точки зрения разрабов этого генератора кода такая информация показалась им излишней и при необходимости её вполне можно добавить в генератор.

В общем, на мой взгляд, вполне неплохое совпадение изложенного в моей публикации (структура, конструктор, метаданные) с практикой (структура, конструктор). Что касается статических методов, то Safary только 26-го апреля этого года научилась понимать статику (хотя того же эффекта можно добиться за счёт прямого переноса методов в класс: ConfigEmail.initialize = function(obj)<> ).

Коллега @nin-jin в своём комментарии справедливо заметил, что хорошо бы «проверять типы полей перед их сохранением«. Я могу согласиться, что было бы правильным приводить типы данных к ожидаемым (нормализовать данные). Кстати, так и делается в OpenAPI-генераторе:

Насколько я понял, функция Rec предназначена для создания простых дата-объектов (объекты без логики, только с данными) «на лету». В общем-то это и есть DTO, только режима runtime, а не уровня кода (те же метаданные в моём примере позволяют программисту «метить» места использования соотв. DTO и находить их без запуска приложения).

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

Источник

Структуры данных в PHP. Data Transfer Object. Предметно-ориентированный Laravel

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

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

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

Возможно, вы сейчас думаете о моделях, но это не совсем так. Сначала нам нужно сделать еще несколько шагов назад.

Теория типов

Чтобы понять как использовать объекты передачи данных (DTO) вам нужно будет иметь некоторые базовые знания о типизации.

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

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

PHP – это слабо типизированный язык:

PHP имеет слабую типизацию. Будучи языком, который в основном работает с HTTP-запросом, практически все является строкой.

Даже при наличии включенной строгой типизации система типов PHP слаба. Подсказки типа обеспечивают только тип переменной в этот момент времени, без гарантии относительно любого будущего значения, которое может иметь эта переменная.

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

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

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

Статические и динамические типы

Есть еще одна концепция, которую мы должны рассмотреть: статические и динамические типы. И именно здесь все начинает становиться интересным.

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

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

Опять же, есть и обратная сторона: поскольку PHP проверяет свои типы только во время выполнения, проверка типов программы может завершиться неудачей при запуске. Это означает, что у вас может быть более четкая ошибка для отладки, но все равно программа потерпела крах.

Эта проверка типов во время выполнения делает PHP динамически типизированным языком. Потому как статически типизированный язык программирования перед выполнением кода выполнит все проверки всех типов в программе при компиляции.

Эти библиотеки могут предложить довольно много информации о вашем коде, без необходимости запускать или модульно тестировать его, IDE, такая как PhpStorm, также имеет множество встроенных статических проверок.

Имея в виду всю эту справочную информацию, пришло время вернуться к ядру нашего приложения: данным.

Структурирование неструктурированных данных

Приходилось ли вам когда-нибудь работать с “массивом данных”, который на самом деле был больше, чем просто список? Вы использовали ключи массива в качестве полей? И вы чувствовали боль от того, что не знали точно, что было в этом массиве? Не будучи уверенным, действительно ли данные в нем такие, какими вы их ожидаете увидеть, или какие поля доступны?

Давайте представим себе, о чем я говорю: работа с запросами Laravel. Рассмотрим этот пример как базовую операцию CRUD для обновления существующего клиента:

Прежде чем искать решения этой проблемы, вот что вы можете сделать, чтобы понять что находится в этой свалке:

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

Оказывается, что строго типизированные системы в сочетании со статическим анализом могут быть большим подспорьем в понимании того, с чем именно мы имеем дело. Такие языки, как Rust, например, решают эту проблему просто:

Структура – это то, что нам нужно! К сожалению, PHP не имеет структур. Но у него есть массивы и объекты. Объектов может быть достаточно для решения нашей проблемы:

Типизированные свойства доступны только с PHP 7.4. В зависимости от того, когда вы читаете эту статью, вы можете еще не использовать их — у меня есть решение для вас позже в этой статье, продолжайте читать.

Для тех, кто может использовать PHP 7.4 или выше, вы можете сделать что-то вроде этого:

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

Этот шаблон упаковки неструктурированных данных в типы, чтобы мы могли использовать наши данные надежным способом, называется “объекты передачи данных” или DTO (Data Transfer Object). Этот шаблон я настоятельно рекомендую вам использовать в ваших проектах большего размера. Для средних и маленьких проектов данный шаблон будет избыточен.

Обсуждая эту статью с коллегами, друзьями или в сообществе Laravel, вы можете столкнуться с людьми, которые не разделяют одно и то же видение сильной типизации. На самом деле есть много людей, которые предпочитают принимать динамическую и слабую типизацию PHP. И тут определенно есть что сказать в свое оправдание.

По моему опыту, однако, есть больше преимуществ в строго типизированном подходе при работе с командой из нескольких разработчиков над проектом в течение серьезного количества времени. Вы должны использовать любую возможность, чтобы уменьшить когнитивную нагрузку. Вы не хотите, чтобы разработчикам приходилось начинать отладку своего кода каждый раз, когда они хотят знать, что именно находится в переменной. Информация должна быть прямо под рукой, чтобы разработчики могли сосредоточиться на том, что важно: создании приложения.

Конечно, использование DTO имеет свою цену: существуют не только накладные расходы на определение этих классов; Вам также нужно сопоставить, например, запрос данных с DTO.

Преимущества использования DTO определенно перевешивают. Независимо от того, сколько времени вы потеряете, написав этот код, вы компенсируете его в долгосрочной перспективе.

Однако вопрос о построении DTO из “внешних” данных все еще нуждается в ответе.

Фабрики DTO

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

Первый-самый правильный: использование специальной фабрики.

Наличие отдельной фабрики сохраняет ваш код чистым на протяжении всего проекта. Эта фабрика должна жить в прикладном слое.

Что плохого в таком подходе? Ну, во-первых: он добавляет специфичную для приложения логику в домен. DTO, который живет в домене, теперь должен знать о CustomerRequest классе, который живет в прикладном слое.

Очевидно, что смешивание кода конкретного приложения в рамках домена-не самая лучшая идея. Однако у него есть мое предпочтение. На то есть две причины.

Во-вторых, и это более важная причина; я предпочитаю этот подход, потому что одно из собственных ограничений PHP: он не поддерживает именованные параметры.

Видите ли, вы не хотите, чтобы ваши DTO в конечном итоге имели конструктор с индивидуальным параметром для каждого свойства: это не масштабируется и очень сбивает с толку при работе со свойствами nullable или default-value. Вот почему я предпочитаю подход передачи массива в DTO, и пусть он строит себя на основе данных в этом массиве.

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

Если бы PHP поддерживал что-то вроде именованных параметров, я бы использовал такой шаблон:

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

Альтернатива типизированным свойствам

Как я уже упоминал ранее, существует альтернатива использованию типизированных свойств для поддержки DTO: docblocks. Пакет DTO от Spatie, который выше применяли, также поддерживает их.

Однако по умолчанию docblocks не дают никаких гарантий, что данные относятся к тому типу, о котором они говорят. К счастью, PHP имеет свой API рефлексии, и с ним возможно гораздо больше.

Решение, предоставляемое этим пакетом, можно рассматривать как расширение системы типизации PHP. Если вы не можете использовать PHP 7.4 и хотите немного больше уверенности в том, что ваши типы docblock действительно соблюдаются, этот пакет вам поможет.

Поскольку данные лежат в основе почти каждого проекта, это один из самых важных строительных блоков. Объекты передачи данных предлагают вам способ работы с данными структурированным, безопасным и предсказуемым способом.

Источник

Переосмысление DTO в Java

Привет, Хабр! Представляю вашему вниманию любительский перевод статьи “Rethinking the Java DTO” Стивена Уотермана, где автор рассматривает интересный и нестандартный подход к использованию DTO в Java.

Я провел 12 недель в рамках программы подготовки выпускников Scott Logic, работая с другими выпускниками над внутренним проектом. И был момент, который застопорил меня больше других: структура и стиль написания наших DTO. Это вызывало массу споров и обсуждений на протяжении всего проекта, но в итоге я понял, что мне нравится использовать DTO.

Данный подход не является единственно верным решением, но он довольно интересный и отлично подходит для разработки с использованием современных IDE. Надеюсь, что изначальный шок пройдет и вам он тоже понравится.

Что такое DTO (Data Transfer Object)?

Зачастую, в клиент-серверных приложениях, данные на клиенте (слой представления) и на сервере (слой предметной области) структурируются по-разному. На стороне сервера это дает нам возможность комфортно хранить данные в базе данных или оптимизировать использование данных в угоду производительности, в то же время заниматься “user-friendly” отображением данных на клиенте, и, для серверной части, нужно найти способ как переводить данные из одного формата в другой. Конечно, существуют и другие архитектуры приложений, но мы остановимся на текущей в качестве упрощения. DTO-подобные объекты могут использоваться между любыми двумя слоями представления данных.

Dto что это такое. Смотреть фото Dto что это такое. Смотреть картинку Dto что это такое. Картинка про Dto что это такое. Фото Dto что это такое

DTO — это так называемый value-object на стороне сервера, который хранит данные, используемые в слое представления. Мы разделим DTO на те, что мы используем при запросе (Request) и на те, что мы возвращаем в качестве ответа сервера (Response). В нашем случае, они автоматически сериализуются и десериализуются фреймворком Spring.

Представим, что у нас есть endpoint и DTO для запроса и ответа:

Что делают хорошие DTO?

Во-первых, очень важно понимать, что вы не обязаны использовать DTO. Это прежде всего паттерн и ваш код может работать отлично и без него.

Они также помогают документировать слой представления в человеко читаемом виде. Мне нравится использовать DTO и, я думаю, вы тоже могли бы их использовать, ведь это к тому же способствует уменьшению зацепления (decoupling) между слоем представления и предметным слоем, позволяя приложению быть более гибким и уменьшая сложность его дальнейшей разработки.

Тем не менее, не все DTO являются хорошими. Хорошие DTO помогают создавать API согласно лучшим практикам и в соответствии с принципам чистого кода.

Они должны позволять разработчикам писать API, которое внутренне согласовано. Описание параметра на одной из конечных точек (endpoint) должно применяться и к параметрам с тем же именем на всех связанных точках. В качестве примера, возьмём вышепредставленный фрагмент кода. Если поле price при запросе определено как “цена с НДС”, то и в ответе определение поля price не должно измениться. Согласованное API предотвращает ошибки, которые могли возникнуть из-за различий между конечными точками, и в то же время облегчает введение новых разработчиков в проект.

DTO должны быть надёжными и сводить к минимуму необходимость в написании шаблонного кода. Если при написании DTO легко допустить ошибку, то вам нужно прилагать дополнительные усилия, чтобы ваше API оставалось согласованным. DTO должны “легко читаться”, ведь даже если у нас есть хорошее описание данных из слоя представления — оно будет бесполезно, если его тяжело найти.

Давайте посмотрим на примеры DTO, а потом определим, соответствуют ли они нашим требованиям.

Покажи нам код!

Этот код на первый взгляд может показаться довольно странным, но не переживайте. В оставшейся части поста я объясню почему я остановился на данной реализации и какие преимущества она дает. Надеюсь, что вам станет всё понятно и вы оцените данный подход.

Он частично основывается на реальном коде из нашего проекта для выпускников, переведенный в контекст интернет-магазина. В нём каждый продукт имеет название, розничную и оптовую цену. Для хранения цены мы используем тип данных Double, но в реальных проектах вы должны использовать BigDecimal.

Мы создаем по одному файлу для каждого контроллера, который содержит базовый enum без значений, в нашем случае это ProductDTO. Внутри него, мы разделяем DTO на те, что относятся к запросам (Request) и на те, что относятся к ответу (Response). На каждый endpoint мы создаем по Request DTO и столько Response DTO сколько нам необходимо. В нашем случае у нас два Response DTO, где Public хранит данные для любого пользователя и Private который дополнительно содержит оптовую цену продукта.

Для каждого параметра мы создаем отдельный интерфейс с таким же именем. Каждый интерфейс содержит один-единственный метод — геттер для параметра, который он определяет. Любая валидация осуществляется через метод интерфейса. В нашем примере, аннотация @NotBlank проверяет что название продукта в DTO не содержит пустую строку.

Для каждого поля который входит в DTO мы реализовываем соответствующий интерфейс. В нашем случае аннотация @Value из библиотеки Lombok делает это за нас, автоматически генерируя геттеры.

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

“Это ужасно!”

Это на самом деле выглядит странно и здесь много необычных моментов. Давайте обсудим несколько из них подробнее.

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

Мы не реализовали методы интерфейсов. Да, выглядит немного странно и я хотел бы найти решение получше. Сейчас мы используем автогенерацию геттеров при помощи Lombok для закрытия контракта и это небольшой хак. Выглядело бы лучше, если бы мы могли объявлять поля сразу в интерфейсе, что позволяло бы создавать DTO в одной строчке кода. Однако, в java нет возможности интерфейсам иметь не статические поля. Если вы будете использовать этот подход в других языках, то возможно ваш код будет более лаконичным.

Это (почти) идеально

Давайте вернемся к нашим требованиям к созданию хорошего DTO. Соотвествует ли им наш подход?

Согласованный синтаксис

Мы определенно улучшили согласованность синтаксиса и это главное почему мы могли бы начать использовать данный паттерн. Каждый API параметр теперь имеет свой синтаксис, определенный через интерфейс. Если DTO содержит опечатку в имени параметра или некорректный тип — код просто не скомпилируется и IDE выдаст вам ошибку. Для примера:

К тому же, когда мы используем валидацию на уровне интерфейса, мы исключаем ситуацию, когда один и тот же параметр на одном endpoint проходит валидацию и не проходит её на другом.

Согласованная семантика

Такой стиль написания DTO улучшает понимание кода через наследование документации. Каждый параметр имеет свою семантику которая определена в геттер методах соответствующего ему интерфейса. Пример:

Как только в DTO мы реализовали данный интерфейс, наша документация автоматически стала доступна через геттер.

Dto что это такое. Смотреть фото Dto что это такое. Смотреть картинку Dto что это такое. Картинка про Dto что это такое. Фото Dto что это такое

Теперь вы гарантировано получаете актуальную и целостную документацию во всех DTO, которые реализовали данный интерфейс. В редких случаях, когда вам нужно добавить API, параметр с уже используемым наименованием и разной семантикой, вам придется создать отдельный интерфейс. Хоть это и неудобно, но заставляет разработчиков задуматься в таких ситуациях, а будущим читателям этого кода понять разницу между схожими параметрами.

Читабельность & Поддерживаемость

Будем честны: в нашем подходе достаточно много шаблонного кода. У нас есть 4 интерфейса, без которых не обойтись, и каждый DTO имеет длинную строку с перечислением интерфейсов. Мы можем вынести интерфейсы в отдельный пакет, что поможет избежать лишних “шумов” в коде c описанием DTO. Но даже после этого, бойлерплейт остается главным недостатком данного подхода, что может оказаться веской причиной для того чтобы использовать другой стиль. Для меня, эти затраты все еще стоят того.

К тому же, мы видим всю структуру наших DTO классов. Посмотрите на код и вы увидите все что вам нужно знать из сигнатуры класса. Каждое поле указано в списке реализованных интерфейсов. Достаточно нажать ctrl + q в IntelliJ и вы увидите список полей.

Dto что это такое. Смотреть фото Dto что это такое. Смотреть картинку Dto что это такое. Картинка про Dto что это такое. Фото Dto что это такое

В нашем подходе мы пишем валидацию единоразово, т.к. она реализуется через методы интерфейса. Создали новое DTO — получили валидацию в подарок, после реализации интерфейса.

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

В java, мы можем реализовать это используя обобщение:

Вывод

Я не жду, что вы сразу же пойдете переписывать все ваши DTO. Но есть несколько деталей которые вы можете почерпнуть для себя:

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *