Cuando implementamos nuestros servicios REST que devuelven objetos de la lógica de negocio de nuestra aplicación se pueden dar dos casos: los objetos que devolvemos son entidades, nuestros objetos no son entidades.
Si los objetos son entidades, estaremos exponiendo todos sus datos y en muchos casos no querremos enviarlo todo. Por ejemplo si guardase en una entidad los valores usuario y password, no debemos enviar dicho password como respuesta del JSON. Ante esta situación podríamos usar la anotación @JsonIgnore que fuerza que ese campo no se envíe, pero se aplicaría a todos los casos.
Si a partir de una entidad, según el caso, quisiéramos mandar un conjunto de campos diferente, la anotación @JsonIngore no es suficiente. En general las buenas prácticas de uso de servicios REST nos indican que siempre devolvamos objetos DTO y no expongamos las entidades de negocio, por tanto debemos seguir esta directriz.
Para evitar que la generación de la respuesta como DTO añada complejidad a nuestro código, es recomendable usar elementos de la programación funcional disponibles desde Java8. El modo de proceder debería ser similar al siguiente:
- Creamos dentro del paquete model ( por ejemplo: es.um.atica.miaplicacion.model) los objetos DTO que vamos a enviar. Si el proyecto es grande y lo tenemos dividido en módulos tendremos diferentes paquetes model por lo que deberemos crear cada DTO en el que corresponda.
- Dentro de nuestras entidades creamos un método toDTO() o varios toXXDTO() (siendo XX un indentificador que aporte valor semántico) que devolverá un objeto del tipo DTO con los datos que requiera de la entidad.
- Modificamos nuestro método REST para que, a partir de una lista de entidades, devuelva una lista de DTO, para esto usaremos programación funcional
Método tradicional:
| Bloque de código |
|---|
@GetMapping("api/{version}/usuarios")
public List<Usuario> listarUsuarios() {
return this.usuarioService.findAll(); //Llamamos al servicio de usuarios que devolverá una lista de todos los usuarios del sistema (son Entidades).
} |
Devolviendo DTO con programación funcional:
| Bloque de código |
|---|
@GetMapping("api/{version}/usuarios")
public List<UsuarioDTO> listarUsuarios() {
return this.usuarioService.findAll().parallelStream().map(Usuario::toDTO).collect(Collectors.toList());
} |
La llamada usando programación funcional hace lo siguiente:
- this.usuarioService.findAll(): devuelve las entidades igual que con la manera tradicional
- parallelStream(): Crea un stream para procesar la lista de manera paralela. Si no nos importa el orden los stream paralelos aprovechan la potencia de la programación concurrente para optimizar los recorridos.
- map(Usuario::toDTO): Esta función cambia cada elemento de la lista por el resultado de ejecutar la expresión Lambda o referencia a método que contiene.
- Usuario::toDTO: Es una referencia a método que ejecuta el método toDTO() de la clase Usuario para cada valor del stream.
- collect(Collectors.toList()): Devuelve el resultado de procesar el stream como una lista.
Nota: Cuando usamos un parallelStream crea tantas tareas paralelas como cores CPU disponibles -1