Nous DTOS
1. Patrò de disseny DTO
El DTO (Data Transfer Object) és un patró de disseny que ens permet transferir dades entre diferents capes d'una aplicació (per exemple, entre la capa de servei i la capa de presentació) sense exposar les entitats de base de dades directament. Això ens ajuda a mantenir un bon encapsulament, millorar la seguretat i optimitzar les respostes enviant només els camps necessaris.
Problema amb exposar entitats:
- Seguretat: L'entitat pot tenir camps sensibles (passwords, tokens) que no vols enviar al client ni que eel client puga modificar (over-posting).
- Encapsulament: L'entitat (
@Entity) està lligada a la base de dades i pot canviar amb el temps i AcoblamentSi l'exposes directament, qualsevol canvi a la BD trenca l'API pública. - Recursions: Department té List
i Employee té Department → JSON infinit. D'alguna manera haurem d'evitar-ho, moltes veegades aplanantles relacions. Per exemple, en comptes de enviar tot el Department dins Employee, només enviar el nom del departament. - Sobrecàrrega de dades: Potser el client només necessita id i nom, i estàs enviant-li tot l'objecte.
Per això, en comptes d'exposar les entitats, creem classes DTO que només contenen els camps que volem transferir. Així, el client només veu el que necessita i podem controlar millor la nostra API.
Els objectes DTO seràn molt (però que molt) similars a les entitats, però només amb els camps que volem exposar. El que si necessitarem és una manera de convertir entre entitats i DTOs (i viceversa) quee siga ràpida i àgil.
2. Creació clàssica de DTO
Anem a explicar la creació de DTO amb unes classes clàssiques a la literatura de Java dins del mon empresarial, les classes Empleat i Departament. Partim de les seues entitats i després crearem els DTOs corresponents.
Com podem veure, a banda dels camps, aquestes entitas tenen una relació bidireccional que pot provocar problemes de recursió infinita a l'hora de convertir a JSON. Per això, en els DTOs, normalment "aplanarem" aquesta relació i només inclourem el nom del departament dins l'empleat, i per exemple el número d'empleats del departament.
Així les classes DTO podrien quedar així:
Ens falta crear els mètodes de conversió entre Empleat i EmpleatDTO, i entre Departament i DepartamentDTO. Dins de les maneres clàssiques, podem fer-ho mitjançant constructors que reben l'entitat, o bé mitjançant un mètode estàtic a la classe DTO.
| Java | |
|---|---|
Respecte dels departaments:
2.1. Builders
Amb la potència de Lombok, s'inclouen els builders, que són una manera molt còmoda de crear objectes complexos sense haver de passar molts paràmetres al constructor. Amb un builder, pots construir l'objecte pas a pas, i és especialment útil quan tens molts camps opcionals.
El builder és un mètodee que s'invoca de manera estàtica i que retorna un objecte que té mètodes per a cada camp, i finalment un build() que crea l'objecte final. Això fa que el codi sigui molt més llegible i fàcil de mantenir. Està present a Lombok mitjançant l'anotació @Builder.
El del departament seria similar.
2.2. Processament amb Streams
Imaginem que tenim una col·lecció d'empleats i volem convertir-los a DTOs. Podem fer-ho de manera molt eficient i elegant amb Streams, evitant bucles i codi repetitiu. Hem d'aplicar el mètode de conversió (constructor o mètode estàtic) a cada element de la col·lecció d'entitats.
| Conversió d'una llista d'Empleats a EmpleatDTOs amb Streams | |
|---|---|
- Obtenim la llista d'empleats de la base de dades. Com veurem a continuació, això es podrà fer des de un repositori.
- Inicia el Stream a partir de la llista d'empleats
- Aplica el mètode de conversió a cada empleat, transformant cada
Empleaten unEmpleatDTO. El resultat és un Stream deEmpleatDTO. - Finalment, recollim el resultat en una llista de DTOs que podem retornar al client.
3. Jackson ObjectMapper
Com hem vist, els mètodes manuals de conversió, be al constructor o be a través d'un mètode estàtic, poden ser molt repetitius i tediosos, especialment quan tenim moltes entitats i molts camps, és el que es coneix com boilerplate code.
Per això, una alternativa molt popular és utilitzar la classe ObjectMapper de la biblioteca Jackson, que Spring Boot ja inclou per defecte. El requisit és simple: tu donam dos classes distintes que tinguen camps que es diguen igual. A partir d'aquí, ObjectMapper farà la conversió automàtica entre les dues classes, sense necessitat de que escrigues codi de conversió manual.
I que passa quan els camps no es diueen igual o volem fer algun aplanat? No tindrem més remei que programar manualment aquestes parts, però hem eliminat la major part del codi repetitiu.
- Inyectem el
ObjectMapperque Spring Boot ja té configurat. - Utilitzem el mètode
convertValue()per a convertir l'objecteempleaten un objecteEmpleatDTO. Aquest camps (id, nom, email, salari) es convertiran automàticament. - Aplanem lo distints: li assignem a
nomDepartamentel nom del departament.
| Conversió amb ObjectMapper | |
|---|---|
Existeixen algunes anotacions de Jackson que ens permeten personalitzar el comportament de la conversió:
@JsonIgnoreper a ometre camps@JsonPropertyper a mapejar camps amb noms diferents.- En relacions bidireccionals, podem usar
@JsonManagedReferencei@JsonBackReferenceper a evitar la recursió infinita:@JsonManagedReferences'aplica a la part "pare" de la relació (per exemple, aEmpleat.departament). Això es fa que, per exemple de un empleat mostres el departament.@JsonBackReferences'aplica a la part "fill" de la relació (per exemple, aDepartament.empleats). Això fa que, per exemple, quan mostres un departament no mostres la llista d'empleats, evitant així la recursió infinita.Si has aplanat les relacions no necessites recòrrer a aquestes anotacions, ja que no hi ha cap camp que referencie l'altre objecte. Això caldrà quan lees relacions continuen en el DTO.
ObjectMapper i els serveis
Com hauràs vist al codi hem de injectar un instància de ObjectMapper. Això ens pot resultar interessant fer-ho a nivell de servei, ja que és on normalment realitzarem les conversions entre entitats i DTOs.
Per tant esperarem al servei si volem fer servir ObjectMapper per a les conversions.
Sí que pots implementar els mètodes de conversió dins de les classes DTO, però en crides a constructors o builders.
4. Exercici. Paquet DTO del nostre projecte
Ara es presenta l'exercici de crear els DTOs i els mètodes de conversió per a les entitats del projecte. El paquet on es trobaran els DTOs serà com.sreaming.dto. Intenta fer-ho per tu mateix abans de mirar la sol·lució, i després compara el teu codi amb el que es presenta a continuació.
Punt de Partida
ls Videos
Les visualitzacions:
amb la clau composta:
| VisualitzacioId.java | |
|---|---|
4.1. Sol·lució
Presentem ací la conversió de dos entitats a DTO, una en la que aplanarem les relacions en les que participa, i altra que mante algunes de les relacions.
S'ha inclos la anotació @Builder de Lombok per a facilitar la creació dels DTOs, i comprovar el seu funcionament.
- Definim els camps que volem exposar al client. Els camps bàsics amb el mateix nom que l'entitat.
- Decidim aplanar les relacions, i en comptes de enviar les llistes d'objectes relacionats, enviem només el número d'elements d'aquestes llistes.
- Comprovem que l'objecte d'origen no siga null per a evitar errors.
- Utilitzem el builder per a construir l'objecte DTO. Assignem manualment els camps que tenen el mateix nom, i després els camps aplanats.
Més endavant veurem que el
ObjectMapperde Jackson ens facilita la feina, sobretot en la part dels camps amb el mateix nom.
El Video és molt similar:
Vegem ara la visualització, que com conté un Usuari i un Video, hem decidit no aplanar les relacions, i per tant incloure dins del VisualitzacioDTO un UsuariDTO i un VideoDTO. Això ens permetrà mostrar més informació d'aquests objectes relacionats).
Per a crear un DTO convertirem altres DTO
Fixa't que ja farem ús de crides als mètodes que hem creat a UsuariDTO i VideoDTO per a convertir els objectes relacionats. Això és una bona pràctica, ja que ens permet reutilitzar el codi de conversió i mantenir una estructura clara i modular.
- Decidim no aplanar lees relacions, i per tant incloure dins del DTO un altre DTO per a cada objecte relacionat.
- Incloem els camps bàsics que volem exposar del Visualitzacio
- Ací convertim l'objecte
Usuarirelacionat aUsuariDTOmitjançant el mètodefromEntity()que vam crear aUsuariDTO. - Ací convertim l'objecte
Videorelacionat aVideoDTOmitjançant el mètodefromEntity()que vam crear aVideoDTO.
Passe'm a veure un concepte avançat, com són els Streams Streams