В каких архитектурных сценариях следует использовать DTO (Data Transfer Object)?

«В каких архитектурных сценариях следует использовать DTO (Data Transfer Object)?» — вопрос из категории Архитектура, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Основные сценарии применения:

  1. Изоляция модели предметной области (Domain Model) от слоя представления (API):

    • Сокрытие внутренней структуры: Не экспонировать поля JPA-сущности (например, @OneToMany связи, служебные поля version).
    • Защита данных: Исключить чувствительные поля (пароли, токены) из ответа API.
    • Адаптация формата: Изменить структуру данных под нужды конкретного клиента (UI, мобильное приложение).
  2. Оптимизация сетевых вызовов (N+1 проблема):

    • Объединение данных из нескольких сущностей в один объект для передачи за один запрос, вместо множества последовательных.
  3. Работа с удаленными интерфейсами (Web API, RPC):

    • Сериализация и десериализация в JSON/XML. DTO часто имеют плоскую структуру, удобную для передачи по сети.

Пример: Отделение Entity от DTO для REST API

// Entity (JPA) — внутренняя модель данных БД
@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private String passwordHash; // Конфиденциальное поле!
    @OneToMany(mappedBy = "user")
    private List<Order> orders; // Детали, не всегда нужные клиенту
    // ... геттеры, сеттеры
}

// DTO для ответа API — содержит только необходимые клиенту данные
public class UserProfileDto {
    private Long id;
    private String username;
    // Поля passwordHash и orders отсутствуют
    private int orderCount; // Вычисляемое поле для клиента

    // Конструктор на основе Entity (маппинг)
    public UserProfileDto(User user) {
        this.id = user.getId();
        this.username = user.getUsername();
        this.orderCount = user.getOrders().size();
    }
    // ... геттеры
}

Использование DTO делает контракт API стабильным и независимым от изменений в модели данных или бизнес-логике.