Dto classes in java

Шаблон DTO (объект передачи данных)

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

2. Узор​

DTO или объекты передачи данных — это объекты, которые переносят данные между процессами, чтобы уменьшить количество вызовов методов. Паттерн был впервые представлен Мартином Фаулером в его книге EAA .

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

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

3. Как его использовать?​

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

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

Изображение ниже иллюстрирует взаимодействие между компонентами:

4. Когда его использовать?​

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

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

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

5. Вариант использования​

Чтобы продемонстрировать реализацию шаблона, мы будем использовать простое приложение с двумя основными моделями предметной области, в данном случае User и Role . Чтобы сосредоточиться на шаблоне, давайте рассмотрим два примера функциональности — поиск пользователей и создание новых пользователей.

5.1. DTO против домена​

Ниже приводится определение обеих моделей:

 public class User     private String id;   private String name;   private String password;   private ListRole> roles;    public User(String name, String password, ListRole> roles)    this.name = Objects.requireNonNull(name);   this.password = this.encrypt(password);   this.roles = Objects.requireNonNull(roles);   >    // Getters and Setters    String encrypt(String password)    // encryption logic   >   > 
 public class Role     private String id;   private String name;    // Constructors, getters and setters   > 

Теперь давайте посмотрим на DTO, чтобы мы могли сравнить их с моделями предметной области.

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

Следовательно, небольшие различия заключаются либо в том, чтобы упаковать вместе запрос, отправленный на сервер, либо в оптимизации ответа клиента:

 public class UserDTO    private String name;   private ListString> roles;    // standard getters and setters   > 

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

Следующий DTO группирует все данные, необходимые для создания пользователя, и отправляет их на сервер в одном запросе, что оптимизирует взаимодействие с API:

 public class UserCreationDTO     private String name;   private String password;   private ListString> roles;    // standard getters and setters   > 

5.2. Подключение обеих сторон​

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

Обычно это происходит на уровне представления:

 @RestController   @RequestMapping("/users")   class UserController     private UserService userService;   private RoleService roleService;   private Mapper mapper;    // Constructor    @GetMapping   @ResponseBody   public ListUserDTO> getUsers()    return userService.getAll()   .stream()   .map(mapper::toDto)   .collect(toList());   >     @PostMapping   @ResponseBody   public UserIdDTO create(@RequestBody UserCreationDTO userDTO)    User user = mapper.toUser(userDTO);    userDTO.getRoles()   .stream()   .map(role -> roleService.getOrCreate(role))   .forEach(user::addRole);    userService.save(user);    return new UserIdDTO(user.getId());   >    > 

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

 @Component   class Mapper    public UserDTO toDto(User user)    String name = user.getName();   ListString> roles = user  .getRoles()   .stream()   .map(Role::getName)   .collect(toList());    return new UserDTO(name, roles);   >    public User toUser(UserCreationDTO userDTO)    return new User(userDTO.getName(), userDTO.getPassword(), new ArrayList>());   >   > 

6. Распространенные ошибки​

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

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

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

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

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

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

Другие шаблоны достигают аналогичного результата, но обычно они используются в более сложных сценариях, таких как CQRS , Data Mappers , CommandQuerySeparation и т. д.

7. Заключение​

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

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

Как обычно, исходный код примера доступен на GitHub .

Источник

Spring — это не страшно, прослойка из DTO

Java-университет

СОДЕРЖАНИЕ ЦИКЛА СТАТЕЙ Продолжаем говорить про Spring. Сегодня будем разбирать паттерн DTO, для понимания можно почитать тут. Самое сложное в DTO — это понять, зачем оно нужно. Давайте займемся спекуляцией овощей, и заодно, попишем код, может по ходу дела что то и проясниться. Создайте spring-boot проект , подключите h2 и Lombok. Создайте пакеты: entities, repositories, services, utils. В entities создайте сущность Product:

 package ru.java.rush.entities; import lombok.Data; import lombok.experimental.Accessors; import org.hibernate.annotations.GenericGenerator; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Accessors(chain = true) @Entity @Data public class ProductEntity < @Id @Column @GenericGenerator(name = "generator", strategy = "increment") @GeneratedValue(generator = "generator") Integer id; @Column String name; @Column Integer purchasePrice;//закупочная цена >

Реализуйте классы ProducRepository, ProducService и класс ItiiateUtil (аналогично прошлой статье). Допустим мы прикупли картофель по оптовой цене 20 рублей за кг., и морковки по 14 рублей за кг. Приобретенные продукты положим в хранилище. Дополним БД записями: [Id =1, name= “Картофель”, purchasePrice = 20] [Id =2, name= “Морковь”, purchasePrice = 14] Как порядочные спекулянты, мы должны выгодно впарить свой товар, для этого давайте красиво упакуем его и накрутим цену. То есть, были у нас грязные и не красивые овощи, наваленные кучей, а станут чистенькие премиум-веган продукты сегмента лакшери. Согласитесь, это будет уже не тот продукт(объект) который мы купили оптом. Для нового продукта создадим пакет dto и в нем класс ProductDto

 package ru.java.rush.dto; import lombok.Data; @Data public class ProductDto < Integer id; String name; Integer purchasePrice; String packaging;//упаковка Integer salePrice;//цена реализации >

У ProductDto есть две переменные, которых нет у ProductEntity: «упаковка» и «цена реализации». Объект dto может содержать точно такие же переменные, как и entity, или их может быть больше, или меньше. Мы помним, что конвертация одного объекта в другой – это дело маппинга. В пакете utils создадим класс MappingUtils

 package ru.java.rush.utils; import org.springframework.stereotype.Service; import ru.java.rush.dto.ProductDto; import ru.java.rush.entities.ProductEntity; @Service public class MappingUtils < //из entity в dto public ProductDto mapToProductDto(ProductEntity entity)< ProductDto dto = new ProductDto(); dto.setId(entity.getId()); dto.setName(entity.getName()); dto.setPurchasePrice(entity.getPurchasePrice()); return dto; >//из dto в entity public ProductEntity mapToProductEntity(ProductDto dto) < ProductEntity entity = new ProductEntity(); entity.setId(dto.getId()); entity.setName(dto.getName()); entity.setPurchasePrice(dto.getPurchasePrice()); return entity; >> 

Просто заполняем поля из одного объекта, аналогичными полями из другого объекта. В классе ProductService реализуем методы для поиска одного продукта или списка продуктов, но перед эти мы конвертируем entity в dto с помощь написанного выше метода.

 private final ProductRepository productRepository; private final MappingUtils mappingUtils; //для листа продуктов мы использовали стрим public List findAll() < return productRepository.findAll().stream() //создали из листа стирим .map(mappingUtils::mapToProductDto) //оператором из streamAPI map, использовали для каждого элемента метод mapToProductDto из класса MappingUtils .collect(Collectors.toList()); //превратили стрим обратно в коллекцию, а точнее в лист >//для одиночного продукта обошлись проще public ProductDto findById(Integer id) < return mappingUtils.mapToProductDto( //в метод mapToProductDto productRepository.findById(id) //поместили результат поиска по id .orElse(new ProductEntity()) //если ни чего не нашли, то вернем пустой entity ); >

Что будет если мы сейчас положим эти овощи на витрину? А давайте посмотрим. Для этого в ItiiateUtil напишем следующий код и запустим.

 System.out.println("\nВитрина магазина"); for (ProductDto dto : productService.findAll())

На выходе получим: Витрина магазина ProductDto(id=1, name=Картофель, purchasePrice=20, packaging=null, salePrice=null) ProductDto(id=2, name=Морковь, purchasePrice=14, packaging=null, salePrice=null) Ну уж, нет! Такие овощи никто не купит: грязные, не упакованы, да и цена продажи не известна. Настало время бизнес логики. Ее реализуем в классе ProductService. Добавим ка сначала в этот класс пару переменных:

 private final Integer margin = 5;//это наша накрутка на цену private final String packaging = "Упаковано в лучшем виде";//так будет выглядеть упаковка 
 // упаковываем товар public void pack(List list) < list.forEach(productDto ->productDto.setPackaging(packaging) ); > // делаем деньги public void makeMoney(List list) < list.forEach(productDto ->productDto.setSalePrice(productDto.getPurchasePrice() * margin) ); > 
 List productDtos = productService.findAll(); productService.pack(productDtos); productService.makeMoney(productDtos); System.out.println("\nВитрина магазина"); for (ProductDto dto : productDtos))

Выполняем: Витрина магазина ProductDto(id=1, name=Картофель, purchasePrice=20, packaging=Упаковано в лучшем виде, salePrice=100) ProductDto(id=2, name=Морковь, purchasePrice=14, packaging=Упаковано в лучшем виде, salePrice=70) Товар красиво упакован, есть цена, но вы где-нибудь видели, что бы на витрине писали цену за которую купили оптом и еще id какой-то. Дорабатываем напильником, написанный выше код:

 List productDtos = productService.findAll(); productService.pack(productDtos); productService.makeMoney(productDtos); System.out.println("\nВитрина магазина"); for (ProductDto dto : productDtos)
 @Service @RequiredArgsConstructor public class InitiateUtils implements CommandLineRunner < private final ProductService productService; @Override public void run(String. args) throws Exception < Listproducts = new ArrayList<>( Arrays.asList( new ProductEntity() .setName("Картофель") .setPurchasePrice(20), new ProductEntity() .setName("Морковь") .setPurchasePrice(14) )); productService.saveAll(products); List productDtos = productService.findAll(); productService.pack(productDtos); productService.makeMoney(productDtos); System.out.println("\nВитрина магазина"); for (ProductDto dto : productDtos) < System.out.println(String.format( "Купите: %s , по цене: %d", dto.getName(), dto.getSalePrice() )); >> > 

Запускаем: Витрина магазина Купите: Картофель , по цене: 100 Купите: Морковь , по цене: 70 Другое дело! Теперь по думаем, что dto принесло хорошего, кроме кучи дополнительного кода: 1. Мы можем совершать бизнес-логику не меняя объекты в БД(допустим ну ненужно нам в этой таблице иметь поля про упаковку и цену продажи). Картофель отлично пролежит в хранилище и без упаковки с ценником, они там даже лишние. 2. В этой строчке List productDtos = productService.findAll() мы создали кэш из объектов с которыми удобно работать в рамках бизнес-логики. Это, если бы мы положили часть товара в подсобку магазина. 3. Это нам позволило, совершить два бизнес действия: упаковка и наценка, но запрос в базу сделали только один раз (запросы в базу довольно тяжелы в плане производительности). Товар можно упаковывать, клеить ценник и выкладывать на витрину — постепенно набирая его из подсобки, а не бегать за ним каждый раз в хранилище. На вопрос: «Зачем так сложно?», люди тоже пытаются найти ответ, почитайте. Следующая статья

Источник

Читайте также:  Fast python slow python
Оцените статью