Salta al contenuto principale

Separare Dati e Logica con i DTO in Spring Boot

Profile picture for user luca77king

In un’applicazione Spring Boot, i DTO (Data Transfer Object) sono strumenti essenziali per gestire il flusso di dati in modo chiaro, sicuro e modulare. Spesso, nelle prime fasi di sviluppo, si tende a utilizzare direttamente le entità JPA nei controller per semplificare la gestione dei dati. Sebbene possa sembrare comodo, questo approccio porta a diversi problemi: si rischia di esporre informazioni sensibili, di creare accoppiamenti stretti tra i layer e di rendere difficile la manutenzione del codice. I DTO risolvono queste criticità separando la rappresentazione dei dati destinata all’API dalla struttura interna delle entità, creando un livello di astrazione che rende l’applicazione più robusta e scalabile.

Ruoli dei DTO: Request, Response e Comunicazione Interna

I DTO hanno molteplici ruoli all’interno di un’applicazione Spring Boot. Il primo e più noto riguarda le API, dove i DTO servono sia a ricevere i dati dalle richieste del client sia a definire i dati da restituire nelle risposte. Un DTO di richiesta può includere regole di validazione per garantire che i dati in ingresso siano coerenti e completi, mentre un DTO di risposta può filtrare informazioni riservate o riorganizzare i dati per renderli più leggibili e coerenti con le esigenze del client.

Tuttavia, i DTO sono altrettanto importanti per il passaggio di dati tra i vari elementi dell’applicazione. In un’architettura complessa, i servizi devono spesso scambiarsi informazioni senza legarsi direttamente alle entità JPA, che sono pensate principalmente per la persistenza. Utilizzando DTO interni, è possibile trasferire solo le informazioni necessarie, aggregare dati provenienti da più entità e ridurre i side-effect indesiderati. Questo approccio facilita la collaborazione tra moduli, migliora la manutenibilità e rende i servizi facilmente testabili. In pratica, i DTO diventano un linguaggio comune tra i componenti dell’applicazione, separando chiaramente responsabilità e riducendo il rischio di errori legati alla manipolazione diretta delle entità.

Creare un DTO in Spring Boot

Un DTO è fondamentalmente una semplice classe Java che contiene i campi necessari e, se richiesto, annotazioni di validazione. Ad esempio, per la creazione di un utente, un DTO potrebbe contenere nome e email con annotazioni come @NotNull e @Email per assicurare che i dati siano corretti. Il controller può utilizzare @Valid per applicare automaticamente queste regole, intercettando eventuali errori prima che i dati raggiungano la logica di business.

public class UserRequestDTO {
    @NotNull
    @Size(min = 3, max = 50)
    private String name;

    @NotNull
    @Email
    private String email;

    // getter e setter
}

public class UserResponseDTO {
    private Long id;
    private String name;
    private String email;

    // getter e setter
}

Gestire la conversione tra DTO ed Entity

Le entità JPA rimangono separate e contengono solo i dati necessari alla persistenza. La conversione tra DTO e entity può essere gestita manualmente, con metodi di mapping statici, oppure tramite librerie dedicate come MapStruct o ModelMapper, che automatizzano il processo e riducono il codice boilerplate. Questo permette di mantenere le entità indipendenti dalle regole di validazione e dalla struttura dei dati esposta alle API, garantendo un codice modulare e facilmente manutenibile.

public class UserMapper {
    public static User toEntity(UserRequestDTO dto) {
        User user = new User();
        user.setName(dto.getName());
        user.setEmail(dto.getEmail());
        return user;
    }

    public static UserResponseDTO toDTO(User user) {
        UserResponseDTO dto = new UserResponseDTO();
        dto.setId(user.getId());
        dto.setName(user.getName());
        dto.setEmail(user.getEmail());
        return dto;
    }
}

Utilizzare i DTO nel Controller

Nel controller, i DTO vengono utilizzati per ricevere le richieste e inviare le risposte. Il controller passa i DTO al servizio, che esegue la logica di business e restituisce entità che vengono poi convertite in DTO di risposta. In questo modo, ogni componente dell’applicazione mantiene responsabilità chiare: il controller gestisce la comunicazione con il client, il servizio gestisce la logica di business e il repository gestisce la persistenza.

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity<UserResponseDTO> createUser(@Valid @RequestBody UserRequestDTO userDTO) {
        User user = userService.createUser(UserMapper.toEntity(userDTO));
        return ResponseEntity.ok(UserMapper.toDTO(user));
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserResponseDTO> getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        return ResponseEntity.ok(UserMapper.toDTO(user));
    }
}

Flusso dati Spring Boot con DTO

          Client (Browser / App)
                  │
                  ▼
          DTO di Request
   (UserRequestDTO con validazioni)
                  │
                  ▼
            Controller
 (Riceve DTO, @Valid, chiama il servizio)
                  │
                  ▼
             Service Layer
  (Applica logica di business, chiama repository)
                  │
                  ▼
           Entity JPA / Repository
   (Salvataggio o recupero dati dal DB)
                  │
                  ▼
             Mapper / DTO Conversion
     (Entity → UserResponseDTO / DTO interno)
                  │
                  ▼
            DTO di Response
       (Filtra o aggrega dati da restituire)
                  │
                  ▼
                  Client

Descrizione del flusso

  1. Client: invia dati tramite API, ad esempio una richiesta POST per creare un utente.

  2. DTO di Request: il controller riceve un DTO di richiesta (UserRequestDTO) che applica regole di validazione (@Valid, @NotNull, ecc.).

  3. Controller: riceve il DTO, verifica la validità dei dati e li passa al servizio.

  4. Service Layer: esegue la logica di business. Può utilizzare DTO interni per passare solo i dati rilevanti tra servizi o trasformare più entità in un singolo DTO aggregato.

  5. Repository / Entity: interagisce con il database tramite JPA. Le entità non vengono mai esposte direttamente al client.

  6. Mapper / Conversione: il servizio converte le entità in DTO di risposta o DTO interni tramite mapper manuali o librerie come MapStruct.

  7. DTO di Response: viene restituito al client filtrando e riorganizzando i dati.

I DTO all’interno dell’applicazione: comunicazione tra servizi

Oltre all’uso per le API, i DTO sono strumenti preziosi per trasferire dati tra servizi o componenti interni. In un’applicazione complessa, non sempre è necessario passare l’intera entità JPA da un servizio all’altro. I DTO interni consentono di spostare solo i dati rilevanti, di aggregare informazioni da più entità e di ridurre la possibilità di modifiche accidentali ai dati persistenti. Questo approccio favorisce la manutenibilità, facilita i test e migliora la leggibilità del codice.

public class UserStatsDTO {
    private Long userId;
    private int postsCount;
    private int commentsCount;

    // getter e setter
}

Un servizio può costruire un UserStatsDTO aggregando informazioni da più repository e passarlo a un altro servizio, come quello delle notifiche, senza esporre direttamente l’entità User completa. In questo modo, ogni servizio riceve solo i dati necessari, e la struttura interna delle entità resta isolata.

Vantaggi concreti dell’uso dei DTO

L’uso dei DTO migliora la sicurezza, perché permette di esporre solo i dati necessari, evitando di rivelare informazioni sensibili. Aumenta la manutenibilità, poiché modifiche interne al modello di dominio non impattano direttamente le API o altri servizi. Ottimizza le prestazioni, trasferendo solo i dati essenziali tra client e server o tra componenti interni, riducendo la complessità della serializzazione e deserializzazione. Inoltre, favorisce la chiarezza e modularità del codice, separando responsabilità e semplificando test e refactoring.

Conclusione

I DTO in Spring Boot sono strumenti indispensabili per costruire applicazioni robuste, sicure e scalabili. Separare entità, servizi e controller, utilizzare DTO per le richieste, le risposte e la comunicazione interna tra servizi consente di avere un flusso di dati chiaro, modulare e manutenibile. L’adozione dei DTO fin dalle prime fasi di sviluppo riduce errori, migliora la sicurezza e facilita la crescita del progetto nel tempo.