что такое dto spring

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 spring. Смотреть фото что такое dto spring. Смотреть картинку что такое dto spring. Картинка про что такое dto spring. Фото что такое dto spring

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

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

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

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

Заключение

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

Источник

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 и находить их без запуска приложения).

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

Источник

Настройка валидации DTO в Spring Framework

Всем привет! Сегодня мы коснёмся валидации данных, входящих через Data Transfer Object (DTO), настроим аннотации и видимости — так, чтобы получать и отдавать только то, что нам нужно.

Итак, у нас есть DTO-класс UserDto, с соответствующими полями:

Я опускаю конструкторы и геттеры-сеттеры — уверен, вы умеете их создавать, а увеличивать в 3-4 раза код смысла не вижу — представим, что они уже есть.

Мы будем принимать DTO через контроллер с CRUD-методами. Опять же, я не буду писать все методы CRUD — для чистоты эксперимента нам хватит пары. Пусть это будут create и updateName.

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

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

Со всеми аннотациями можно ознакомиться в библиотеке javax.validation.constraints. Итак, настроим наше DTO таким образом, чтобы сразу получать валидированый объект для дальнейшего перевода в сущность и сохранения в БД. Те поля, которые должны быть заполнены, мы пометим NotNull, также пометим e-mail:

Мы задали настройки валидации для DTO — должны быть заполнены все поля, кроме id — он генерируется в БД. Добавим валидацию в контроллер:

Настроенная таким образом валидация подойдёт к созданию нового пользователя, но не подойдёт для обновления существующих — ведь для этого нам нужно будет получить id (который задан как null), а также, пропустить поля login, password и email, поскольку в updateName мы изменяем только имя. То есть, нам нужно получить id и name, и ничего больше. И здесь нам потребуются интерфейсы видимости.

Создадим прямо в классе DTO интерфейс (для наглядности, я рекомендую выносить такие вещи в отдельный класс, а лучше, в отдельный пакет, например, transfer). Интерфейс будет называться New, второй будет называться Exist, от которого мы унаследуем UpdateName (в дальнейшем мы сможем наследовать от Exist другие интерфейсы видимости, мы же не одно имя будем менять):

Теперь мы пометим наши аннотации интерфейсом New.

Теперь эти аннотации работают только при указании интерфейса New. Нам остаётся только задать аннотации для того случая, когда нам потребуется апдейтить поле name (напомню, нам нужно указать не-нулловвыми id и name, остальные нулловыми). Вот как это выглядит:

Теперь нам осталось задать необходимые настройки в контроллерах, прописать интерфейс, чтобы задать валидацию:

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

Итак, мы разобрались, как валидировать входные данные, теперь осталось валидировать выходные. Это делается при помощи аннотации @JsonView.

Сейчас в выходном DTO, который мы отдаём обратно, содержатся все поля. Но, предположим, нам не нужно никогда отдавать пароль (кроме исключительных случаев).

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

Теперь мы можем аннотировать поля так, как нам нужно (видны все, кроме пароля):

Осталось пометить нужные методы контроллера:

А когда-нибудь в другой раз мы пометим аннотацией @JsonView(AdminDetails.class) метод, который будет дёргать только пароль. Если же мы хотим, чтобы админ получал всю информацию, а не только пароль, аннотируем соответствующим образом все нужные поля:

Надеюсь, эта статья помогла разобраться с валидацией входных DTO и видимостью данных выходных.

Источник

Как начать писать микросервис на Spring Boot, чтобы потом не болела голова

Привет! Меня зовут Женя, я Java-разработчик в Usetech, в последнее время много работаю с микросервисной архитектурой, и в этой статье хотела бы поделиться некоторыми моментами, на которые может быть полезно обратить внимание, когда вы пишете новый микросервис на Spring Boot.

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

1. Оставляем контроллеры тонкими

В традиционной слоистой архитектуре класс контроллера принимает запросы и направляет их сервису, а сервис занимается бизнес-логикой. Однако иногда в методах контроллера можно встретить какие-либо проверки входных параметров, а также преобразование Entity в DTO.

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

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

Метод контроллера после рефакторинга:

Метод сервиса после рефакторинга:

2. Используем разные DTO для разных случаев

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

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

3. Вычищаем WARN-ы, пока их мало

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

Пример WARN, который выводится даже у «пустого» Spring Boot 2 приложения с Hibernate:
spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

Это предупреждение сообщает, что по-умолчанию в Spring Boot 2 включен режим Open Session In View, при котором сессия Hibernate держится открытой все время обработки HTTP-запроса.

Хотя режим Open Session In View позволяет избежать LazyInitializationException, которое возникает если мы пытаемся получить данные, когда сессия Hibernate уже закрыта, тем не менее, его использование является анти-паттерном. Этот режим провоцирует проблемы с производительностью приложения, поскольку приложение держит долгое соединение с базой данных, а также значительно увеличивается количество запросов, так как каждая связанная с сущностью коллекция будет загружена отдельным запросом (проблема n+1). Подробнее об этом можно прочитать в статье.

Для того, чтобы отключить режим Open Session In View нужно сделать как раз то, что написано в предупреждении — добавить в application.yml настройку:

4. Кэшируем контекст в тестах

Что заставляет контекст перезапускаться заново:

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

Иногда в этом классе также может быть удобно держать метод очистки, запускаемый после каждого теста ( @AfterEach ), однако это уже дело вкуса/особенностей тестов.

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

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

Источник

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

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

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

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

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

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

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

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 spring. Смотреть фото что такое dto spring. Смотреть картинку что такое dto spring. Картинка про что такое dto spring. Фото что такое dto spring

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

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

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

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

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

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

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

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

Вывод

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

Источник

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

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