Перейти к основному содержимому

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

· 5 мин. чтения

1. Обзор

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

2. Узор

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

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

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

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

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

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

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

./6588a64599def47c73b4cd2c6c566dcb.svg

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

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

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

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

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

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

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

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

public class User {

private String id;
private String name;
private String password;
private List<Role> roles;

public User(String name, String password, List<Role> 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 List<String> roles;

// standard getters and setters
}

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

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

public class UserCreationDTO {

private String name;
private String password;
private List<String> 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 List<UserDTO> 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();
List<String> 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 .