← Назад к вопросам

Когда не стоит использовать DTO?

1.0 Junior🔥 261 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

DTO: когда НЕ использовать

DTO (Data Transfer Object) — это популярный паттерн проектирования, но его избыточное использование может привести к излишней сложности кода. Давайте разберём, когда DTO не нужны.

Проблемы с чрезмерным использованием DTO

1. Простые CRUD операции в monolith'е

Для небольших монолитных приложений, где нет чётких границ между слоями, использование DTO может быть оверинжинирингом:

// Плохо: DTO для простого случая
public class UserDTO {
    private Long id;
    private String name;
    private String email;
    // ... getters/setters
}

public class UserService {
    public UserDTO getUserById(Long id) {
        User user = userRepository.findById(id).orElseThrow();
        // Лишнее преобразование
        return new UserDTO(user.getId(), user.getName(), user.getEmail());
    }
}

@RestController
public class UserController {
    public ResponseEntity<UserDTO> getUser(Long id) {
        // Двойное преобразование
        return ResponseEntity.ok(userService.getUserById(id));
    }
}

// Хорошо: вернуть entity напрямую
@RestController
public class UserController {
    public ResponseEntity<User> getUser(Long id) {
        return ResponseEntity.ok(userRepository.findById(id).orElseThrow());
    }
}

2. Когда structure объекта в API совпадает с Entity

Если то, что вы отправляете в API, идентично тому, как хранится в БД:

// DTO здесь лишний
@Entity
public class Product {
    @Id
    private Long id;
    private String name;
    private BigDecimal price;
    private String description;
}

// Зачем создавать DTO если он полностью повторяет Entity?
public class ProductDTO {
    private Long id;
    private String name;
    private BigDecimal price;
    private String description;
}

// Просто используй Entity
@RestController
public class ProductController {
    @GetMapping("/products/{id}")
    public ResponseEntity<Product> getProduct(@PathVariable Long id) {
        return ResponseEntity.ok(productRepository.findById(id).orElseThrow());
    }
}

3. Внутренние вызовы между сервисами

Для inter-service общения внутри одного приложения DTO может быть излишним:

// Не нужен DTO для внутреннего вызова
@Service
public class OrderService {
    @Autowired
    private InventoryService inventoryService;
    
    // Плохо
    public CreateOrderResponse createOrder(CreateOrderRequest request) {
        InventoryDTO inventory = inventoryService.checkInventory(request.getItemId());
        // ...
    }
    
    // Хорошо: вернуть Entity напрямую
    public Order createOrder(CreateOrderRequest request) {
        Inventory inventory = inventoryService.checkInventory(request.getItemId());
        // ...
    }
}

4. WebSocket или Server-Sent Events

Для real-time событий DTO может усложнить код:

// Плохо: излишний DTO
public class MessageDTO {
    private String text;
    private LocalDateTime timestamp;
}

@RestController
@CrossOrigin
public class ChatController {
    @Autowired
    private SimpMessagingTemplate messagingTemplate;
    
    @PostMapping("/send")
    public void sendMessage(MessageDTO dto) {
        // преобразование
        messagingTemplate.convertAndSend("/topic/chat", dto);
    }
}

// Хорошо: используй Entity напрямую
@RestController
public class ChatController {
    @PostMapping("/send")
    public void sendMessage(Message message) {
        messagingTemplate.convertAndSend("/topic/chat", message);
    }
}

5. GraphQL API

GraphQL автоматически решает проблему овер-фетчинга, поэтому DTO часто не нужны:

// DTO здесь точно не нужен
@QueryMapping
public User user(@Argument Long id) {
    // GraphQL сам выберет только нужные поля
    return userRepository.findById(id).orElseThrow();
}

Когда DTO действительно нужны

1. Микросервисная архитектура

// Нужен DTO для изоляции внутреннего состояния
@Entity
public class User {
    @Id
    private Long id;
    private String name;
    private String internalSecret;  // Это не должно уходить в API
}

// DTO для публичного API
public class PublicUserDTO {
    private Long id;
    private String name;
    // internalSecret НЕ включен
}

@RestController
public class UserController {
    @GetMapping("/api/v1/users/{id}")
    public ResponseEntity<PublicUserDTO> getUser(@PathVariable Long id) {
        User user = userRepository.findById(id).orElseThrow();
        return ResponseEntity.ok(mapToDTO(user));
    }
}

2. Разные view одного Entity

// Один Entity — разные представления для разных клиентов
@Entity
public class Order {
    @Id
    private Long id;
    private String orderNumber;
    private BigDecimal totalAmount;
    private String customerNotes;
    private String internalNotes;
}

// Для публичного API
public class PublicOrderDTO {
    private Long id;
    private String orderNumber;
    private BigDecimal totalAmount;
}

// Для внутреннего систем
public class InternalOrderDTO {
    private Long id;
    private String orderNumber;
    private BigDecimal totalAmount;
    private String customerNotes;
    private String internalNotes;
}

@RestController
public class OrderController {
    @GetMapping("/public/orders/{id}")
    public ResponseEntity<PublicOrderDTO> getPublicOrder(@PathVariable Long id) {
        Order order = orderRepository.findById(id).orElseThrow();
        return ResponseEntity.ok(mapToPublicDTO(order));
    }
}

3. Валидация входящих данных

// DTO для валидации и трансформации входящих данных
public class CreateUserDTO {
    @NotBlank(message = "Name is required")
    private String name;
    
    @Email(message = "Invalid email")
    private String email;
    
    @Min(value = 18, message = "Must be 18 or older")
    private Integer age;
}

@RestController
public class UserController {
    @PostMapping("/api/v1/users")
    public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserDTO dto) {
        User user = new User(dto.getName(), dto.getEmail());
        return ResponseEntity.created(null).body(userRepository.save(user));
    }
}

4. Агрегирование данных из нескольких Entity

// DTO для комплексного представления
public class OrderWithItemsDTO {
    private Long orderId;
    private String orderNumber;
    private List<OrderItemDTO> items;
    private CustomerDTO customer;
}

@Service
public class OrderService {
    public OrderWithItemsDTO getOrderDetails(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        
        // Собираем данные из разных источников
        List<OrderItemDTO> items = order.getItems().stream()
            .map(this::mapToItemDTO)
            .collect(Collectors.toList());
        
        CustomerDTO customer = mapCustomerToDTO(order.getCustomer());
        
        return new OrderWithItemsDTO(order.getId(), order.getNumber(), items, customer);
    }
}

Практические рекомендации

НЕ используй DTO если:

  • Приложение — простой monolith
  • Entity и его API представление идентичны
  • Это внутренние вызовы сервисов
  • Используешь GraphQL

Используй DTO если:

  • Микросервисная архитектура
  • Разные представления для разных клиентов
  • Нужна валидация входящих данных
  • Агрегирование данных из нескольких источников
  • Нужно скрыть внутренние поля Entity

Главный принцип: DTO должны решать реальные проблемы, а не быть преждевременной оптимизацией!

Когда не стоит использовать DTO? | PrepBro