Progettazione REST API e DTO: un regalo senza confezione è rischioso

“Eh? Perché si vede la password?”

All’inizio della mia esperienza in azienda, sviluppavo freneticamente passando avanti e indietro tra frontend e backend. Un giorno stavo collegando alla schermata frontend la ‘API per l’elenco dei membri’ sviluppata da un collega entrato in azienda insieme a me.

Ho aperto la scheda Network degli strumenti di sviluppo di Chrome (F12) per controllare se i dati arrivassero correttamente e, nel momento in cui ho visto la risposta, ho pensato di aver visto male.

[
  {
    "id": 1,
    "username": "tester",
    "password": "$2a$10$D...", // Password cifrata esposta
    "ssn": "900101-1...",      // Codice fiscale esposto
    "createdAt": "2024-01-01"
  }
]

Spaventato, gli ho chiesto: “Ehi, perché nella risposta di questa API si vedono la password e perfino il numero di documento?”

Lui ha risposto con nonchalance: “Ah, quello? Tanto a schermo mostriamo solo il nome. Mi scocciava farlo bene, quindi ho restituito direttamente il risultato di userRepository.findAll(). C’è qualche problema?”

Mi è corso un brivido lungo la schiena. Lui stava solo cercando comodità, ma in pratica aveva piantato il seme di un grave incidente di sicurezza.

“Solo perché non si vede a schermo non vuol dire che sia sicuro!”

Chiunque può aprire la scheda Network del browser. Cosa succederebbe se un attaccante malevolo chiamasse questa API?

Esportare un’entità così com’è equivaleva a trasformare la camera da letto di casa nostra in una parete completamente di vetro.

Una busta trasparente che lascia vedere tutto il contenuto è praticamente un invito per i ladri.

DTO: la ‘carta regalo’ dei dati

Un’‘Entity’ è una ‘materia prima’ che si maneggia solo all’interno della fabbrica. Porta addosso cose che non dovrebbero mai essere mostrate al cliente, come segreti (password) o informazioni di gestione interna (data di creazione, data di modifica, flag di cancellazione).

Un ‘DTO(Data Transfer Object)’, invece, è il ‘prodotto finito’, ben confezionato per essere consegnato al cliente.

Restituire direttamente un’entità è come servire a un cliente, invece di una bistecca cotta, un pezzo di carne cruda e sanguinante lanciato nel piatto. Dobbiamo assolutamente passare per una fase di ‘mapping’.

[Code Verification] La palude del ciclo infinito (riferimento circolare)

Più dei problemi di sicurezza, ciò che fa impazzire gli sviluppatori è il problema dei ‘riferimenti circolari’. Ti ricordi il mapping bidirezionale JPA visto l’altra volta, User <-> Team?

Cosa succede se restituisci direttamente l’entità User convertita in JSON?

@Entity
public class User {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne // Un utente appartiene a un team
    private Team team;
}

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team") // Un team ha piu utenti
    private List<User> users = new ArrayList<>();
}

Se restituisci User in questo stato, il serializzatore JSON (Jackson) si comporta così.

Risultato: il server cade eroicamente con un StackOverflowError. Le relazioni tra entità continuano a inseguirsi all’infinito, quindi se le esponi senza spezzare quella catena, precipiti in un ciclo infinito. Questo è il motivo decisivo per usare un DTO e contenere solo i campi necessari, come teamName.

Se non spezzi la catena delle relazioni con un DTO, i dati continueranno a girare per sempre.

La condizione di una buona API: essere RESTful

Se hai già confezionato bene i dati con i DTO, adesso devi scegliere bene l’‘indirizzo’ a cui spedire quel pacco. Ed è proprio questo il senso della progettazione di una REST API.

Ai tempi dell’università, davo alle URL i nomi che mi passavano per la testa.

Non è una buona idea inserire verbi come join o update nella URL. In una progettazione RESTful, la ‘risorsa’ (sostantivo) sta nella URL, mentre l’‘azione’ (verbo) viene affidata al metodo HTTP.

Costruendola così, basta guardare la URL per capire: “Ah, qui si sta gestendo la risorsa utente.” Inoltre comunicare con i frontend developer diventa molto più semplice. Questo è l’accordo implicito condiviso dagli sviluppatori di tutto il mondo.

Consiglio pratico: il nome fa già metà del lavoro (strategia di naming dei DTO)

Quando apro il codice del progetto attuale in azienda, a volte mi scappa subito un sospiro. UserDto, MemberVo, InfoParam, ResultData… chi c’era prima ha dato i nomi come gli pareva, quindi non puoi capire se un oggetto serve a ricevere una richiesta o a restituire una risposta finché non vai a leggere il codice.

Un DTO è il ‘contenitore’ dei dati. Se lo scopo di quel contenitore non è scritto nel nome, nasce la classica confusione per cui metti il riso nella scodella della zuppa e l’acqua nella ciotola del riso. La regola di naming che consiglio di più nel lavoro reale è questa.

public class UserDto {
    // Richiesta di registrazione
    public static class SignUpRequest {
        private String email;
        private String password;
    }

    // Risposta con le info utente
    public static class Response {
        private String name;
        private String email;
    }
}

Chiudendo la serie 2: ora la casa è costruita

Con questo si chiude la serie [Monolith Builder]. Abbiamo gettato le fondamenta con Spring Boot (DI), separato frontend e backend (CORS), imparato a usare il database con JPA e anche a comunicare attraverso DTO e REST API.

Ora abbiamo acquisito la capacità di costruire da soli un servizio web davvero dignitoso. Ma il viaggio di uno sviluppatore non finisce qui. A che serve se il servizio che ho creato funziona solo sul mio computer, cioè su localhost? Per mostrarlo a utenti reali, bisogna portarlo su un server.

Peccato che quel server non sia Windows, ma Linux: uno schermo nero e basta. Senza nemmeno un mouse, come si installano ed eseguono i programmi lì sopra?

Nella prossima serie, [Works on My Machine], entriamo nel mondo di Linux e Docker per risolvere il problema eterno degli sviluppatori: “Sul mio computer funziona, ma sul server no.”

Lascia un commento