Mapping
Mapping no es técnicamente un cross-cutting concern. No es un filter, aspect, o interceptor. Pero lo encontrarás tan frecuentemente en proyectos reales que vale la pena configurarlo temprano con la ayuda de una librería junto con tus otras decisiones de infraestructura. Por eso lo cubrimos aquí.
En el documento de primer endpoint, FilmRestMapper era una clase escrita a mano que convertía un modelo de dominio Film en un DTO FilmResponse campo por campo. Eso funciona para siete campos. Deja de ser aceptable cuando hay decenas de campos, objetos anidados y conversiones de tipo que gestionar, y nunca genera errores en tiempo de compilación cuando se agrega un campo nuevo al dominio, pero se olvida en el mapper.
Este documento reemplaza el mapper escrito a mano con una librería especializada: MapStruct para Java y Kotlin, ModelMapper para Groovy.
Archivos modificados
- Java
- Kotlin
- Groovy
El archivo de build recibe nuevas dependencias, y FilmRestMapper se reemplaza. Para Groovy, el controller también cambia porque el mapper ya no es una utilidad estática, sino un bean gestionado por Spring.
Agregar la dependencia
- Java
- Kotlin
- Groovy
def mapstructVersion = '1.7.0.Beta1'
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
kotlin("kapt") version "2.3.10"
val mapstructVersion = "1.7.0.Beta1"
implementation("org.mapstruct:mapstruct:$mapstructVersion")
kapt("org.mapstruct:mapstruct-processor:$mapstructVersion")
implementation 'org.modelmapper:modelmapper:3.2.6'
- Java y Kotlin usan MapStruct.
- Kotlin además requiere el plugin
kaptporque MapStruct genera código en tiempo de compilación mediante procesamiento de anotaciones, ykaptes el ejecutor de procesadores de anotaciones de Kotlin.
- Kotlin además requiere el plugin
- Groovy usa ModelMapper, una librería basada en reflection. A diferencia de MapStruct, no requiere procesamiento de anotaciones. Funciona completamente en tiempo de ejecución, haciendo coincidir campos por nombre y tipo.
(Solo Groovy) Configurar ModelMapper
Groovy declara ModelMapperConfig, una clase @Configuration estándar que expone un bean ModelMapper. ModelMapper no tiene estado y es thread-safe, por lo que una única instancia compartida en toda la aplicación es la elección correcta.
Reemplazar FilmRestMapper
- Java
- Kotlin
- Groovy
package dev.pollito.spring_java.sakila.film.adapter.in.rest;
import static org.mapstruct.MappingConstants.ComponentModel.SPRING;
import dev.pollito.spring_java.sakila.film.adapter.in.rest.dto.FilmResponse;
import dev.pollito.spring_java.sakila.film.domain.model.Film;
import org.mapstruct.Mapper;
@Mapper(componentModel = SPRING)
public interface FilmRestMapper {
FilmResponse map(Film source);
}
package dev.pollito.spring_kotlin.sakila.film.adapter.`in`.rest
import dev.pollito.spring_kotlin.sakila.film.adapter.`in`.rest.dto.FilmResponse
import dev.pollito.spring_kotlin.sakila.film.domain.model.Film
import org.mapstruct.Mapper
import org.mapstruct.MappingConstants.ComponentModel.SPRING
@Mapper(componentModel = SPRING)
interface FilmRestMapper {
fun map(source: Film): FilmResponse
}
- Java y Kotlin ahora declaran
FilmRestMappercomo una interfaz. La anotación@Mapper(componentModel = SPRING)le indica a MapStruct que genere una implementación gestionada por Spring en tiempo de compilación. - Groovy reemplaza la clase de utilidad estática con un
@Componentde Spring. Recibe el beanModelMappermediante inyección por constructor y delega la conversión en él.ModelMapper.map(source, FilmResponse)inspecciona ambas clases en tiempo de ejecución y copia los campos coincidentes por nombre y tipo.
(Solo Groovy) Actualizar el controller
El controller original de Groovy importaba FilmRestMapper.convert como un método estático. Ahora que FilmRestMapper es un bean de Spring, el controller necesita recibirlo mediante inyección.
Los controllers de Java y Kotlin no necesitan actualizarse. Ya inyectaban FilmRestMapper como campo, y esa inyección sigue funcionando igual, solo que ahora apunta a la implementación generada por MapStruct en lugar de la clase escrita a mano.
Con la librería de mapping en su lugar, FilmRestMapper ya no se mantiene a mano. MapStruct genera las implementaciones de Java y Kotlin en tiempo de compilación, y ModelMapper maneja Groovy en tiempo de ejecución. Ambos enfoques se integran con la inyección de dependencias de Spring, por lo que el resto de la aplicación permanece sin cambios.