Ответ
В моей практике DTO — это простые объекты, предназначенные исключительно для передачи данных между слоями приложения или между сервисами. Они решают несколько ключевых проблем:
-
Изоляция доменной модели от слоя представления/API: DTO предотвращает «просачивание» логики домена или внутренней структуры сущностей во внешние контракты (например, REST API). Это защищает домен от изменений, требуемых внешними потребителями.
- Без DTO: API напрямую возвращает сущность
Userс полямиpasswordHash,createdAt, которые клиенту не нужны. - С DTO: API возвращает
UserResponseDto, содержащий толькоid,email,name.
- Без DTO: API напрямую возвращает сущность
-
Снижение связности между слоями: Контроллеры или внешние клиенты зависят от стабильного контракта DTO, а не от изменчивой внутренней доменной модели.
-
Оптимизация сетевых запросов: DTO позволяют формировать ответ, содержащий именно те данные, которые нужны клиенту, объединяя информацию из нескольких сущностей (например, заказ + данные клиента), избегая проблемы over-fetching или under-fetching.
-
Стандартизация и валидация входящих данных: DTO на входе API — это удобное место для аннотаций валидации.
Пример на C#:
// Внутренняя доменная сущность
public class User {
public int Id { get; private set; }
public string Email { get; private set; }
public string PasswordHash { get; private set; }
public DateTime CreatedAt { get; private set; }
// Доменные методы...
}
// DTO для ответа API
public record UserResponseDto(int Id, string Email, string FullName);
// DTO для запроса на создание пользователя с валидацией
public record CreateUserRequestDto {
[Required, EmailAddress]
public string Email { get; init; }
[Required, MinLength(6)]
public string Password { get; init; }
[Required]
public string FullName { get; init; }
}
// В контроллере
[HttpPost]
public IActionResult CreateUser([FromBody] CreateUserRequestDto request) {
// Автоматическая валидация DTO сработает до этого места
var user = _userService.CreateUser(request.Email, request.Password, request.FullName);
var response = new UserResponseDto(user.Id, user.Email, user.FullName);
return Ok(response);
}
Основной компромисс — это необходимость написания маппинга (например, с помощью AutoMapper) между доменными объектами и DTO, что добавляет шаблонного кода.