Java Converter Pattern

En Java como en cualquier otro lenguaje de programación, es común encontrarnos con la necesidad de realizar conversión de tipos de datos, sobre todo, aquellos tipos de datos de Entidad que tiene una relación directa con un DTO que utilizamos para enviar los datos del servidor a un cliente o aplicación web, lo que hace que tengamos que convertir la Entidad a DTO para enviarla al cliente y de DTO a Entidad para persistirla en la base de datos, lo cual es una tarea cansada y repetitiva que podemos evitar mediante el uso del patrón Converter.

Si no sabes que es un DTO te dejo la siguiente liga a mi artículo
Data Transfer Object (DTO), donde explico a detalle este patrón.

Pues como lo acabo de decir, convertir tipos de datos es una tarea que se presenta mucho en las aplicaciones, pero sobre todo en la capa de negocio o servicios, donde tenemos que enviar un tipo de dato amigable para el cliente o navegador pero en el Backend necesitamos otros tipos de datos. Para evitar esta tarea tan repetitiva se puede utilizar el patrón Converter, el cual permite encapsular la lógica de conversión de dos tipos de datos de forma bidireccional, de esta forma, evitamos tener que repetir la lógica de conversión de los tipos de datos en todas las partes del programa donde sea requerido, y en su lugar, delegamos esta responsabilidad a una clase externa.

Para analizar esta problemática, analizaremos el caso típico de la Entidad Usuario, la cual tiene por lo general un id, username, password y los roles, tal como podemos ver a continuación:

package com.oscarblancarteblog.entity;

import java.util.List;
import com.oscarblancarteblog.enums.Role;

public class User {
	
	private Long id;
	private String username;
	private String password;
	private List<Role> roles;

	/** GET & SET */
}

Esta entidad es utiliza por el Backend para persisitir la información en la base de datos, sin embargo, no es la que utilizamos para enviar a los clientes, ya que por ejemplo, el campo `roles` es de un tipo de Enumeración, el cual no puede interpretar el cliente que quizás consuma el backend por medio de un API REST, por lo que es posible que en lugar de enviarle una lista de Roles, le tendremos que mandar un lista de Strings, lo que nos obliga a crear un DTO que se ve de la siguiente manera:

import java.util.List;

public class UserDTO {
	private Long id;
	private String username;
	private String password;
	private List<String> roles;

	/* GET & SET */
}

Este DTO ya es más amigable para el cliente, pero ahora tenemos un problema, como crear el DTO a partir del Entity y luego, cuando nos lo manden de regreso para guardarlo o actualizarlo, hay que hacer el proceso inverso. Por simple que parezca, esta es un tarea que se puede repetir muchas veces en la aplicación, creando mucho código redundante y que cada repetición puede inyectar errores, además, un cambio en la Entity o en el DTO, nos obliga a refactorizar todas las secciones donde habríamos echo la conversión, por este motivo, siempre es mejor crear una clase que se encargue exclusivamente de convertir entre Entity y DTO.

Para solucionar este problema y tener un mejor orden en nuestro código, deberemos crear una clase base de la cual extiendan todos los Converters de nuestra aplicación:

package com.oscarblancarteblog.converter;

public abstract class AbstractConverter<E,D> {

	public abstract E fromDto(D dto);

	public abstract D fromEntity(E entity);
}

Esta clase abstracta define dos método, fromEntity que convierte una Entidad a DTO y fromDto que hace lo inverso, adicional , la clase define dos tipos genéricos E para representar al tipo Entidad y D para representar al DTO.

El siguiente paso es crear implementaciones concretas del Converter, por que deberá existir una implementación por cada Par Entidad-DTO. En este caso solo tenemos la clase User, por lo que crearemos el Convertidor para esta clase:

package com.oscarblancarteblog.converter;

import java.util.stream.Collectors;

import com.oscarblancarteblog.dto.UserDTO;
import com.oscarblancarteblog.entity.User;
import com.oscarblancarteblog.enums.Role;

public class UserConverter extends AbstractConverter<User, UserDTO>{

	@Override
	public User fromDto(UserDTO dto) {
		User user = new User();
		user.setId(dto.getId());
		user.setUsername(dto.getUsername());
		user.setPassword(dto.getPassword());
		
		// Prevent NullPointerException
		if(dto.getRoles()!=null) {
			user.setRoles(dto.getRoles().stream().map(rol -> Role.valueOf(rol)).collect(Collectors.toList()));
		}
		return user;
	}

	@Override
	public UserDTO fromEntity(User entity) {
		UserDTO user = new UserDTO();
		user.setId(entity.getId());
		user.setUsername(entity.getUsername());
		user.setPassword(entity.getPassword());
		
		// Prevent NullPointerException
		if(entity.getRoles()!=null) {
			user.setRoles(entity.getRoles().stream().map(rol -> rol.name()).collect(Collectors.toList()));
		}
		return user;
	}
}

Esta nueva clase se encargará de convertir los dos tipos de datos dejando en un solo lugar la lógica de conversión y evitando tener que repetir esta lógica en toda nuestro código.

Podrás observar que esta clase no tiene gran ciencia, pues es básicamente tomar los valores de un objeto y pasarlos a otro objeto pero diferente tipo de datos, nada del otro mundo.

Ahora bien, ya con esto, ¿como le haríamos para convertir los datos?, pues ya solo falta instanciar al convertidor y utilizarlo. Imaginemos que tenemos un método que consulta un usuario:

public UserDTO findUserByUsername(String username) {
	User user = userDAO.findByUsername(username);
	UserConverter converter = new UserConverter();
	return converter.fromEntity(user);
}

En este método ya podemos observar las bondades del converter, pues nos deja un código muy limpio y no tenemos que preocuparnos por convertir la Entidad a DTO. Pero que pasaría si ahora nos mandan el Usuario como DTO para actualizarlo en la base de datos:

public void save(UserDTO userDto) {
	UserConverter converter = new UserConverter();
	User userEntity = converter.fromDto(userDto);
	userDAO.save(userEntity);
}

Nuevamente vemos como el converter nos ha salvado de nuevo, pues nos olvidamos de la lógica de conversión.

Pero que pasaría ahora si en lugar de buscar un solo usuario, necesitamos que nos regrese todos los usuarios, bueno, estos nos obligaría a retornar una lista en lugar de un solo usuario, por lo que tendriamos que implementar una lógica para convertir listas. Lo primero que se nos puede ocurrir es crear un for en el servicio anterior y convertir cada uno de los objetos, crear una nueva lista a partir de los resultados y retornar el valor, pero esto traerá el mismo problema que hablamos al inicio, pues tendríamos que repetir este proceso cada vez que necesitemos convertir una lista. Por suerte contamos con una clase base llamada AbstractConverter, ¿la recuerdas?

Que te parece si entonces aprovechamos esta clase base para agregar los método de conversión de listas y de esta forma, se podrán utilizar en todos los converters:

package com.oscarblancarteblog.converter;

import java.util.List;
import java.util.stream.Collectors;

public abstract class AbstractConverter<E,D> {

	public abstract E fromDto(D dto);
	
	public abstract D fromEntity(E entity);
	
	public List<E> fromDto(List<D> dtos){
		if(dtos == null) return null;
		return dtos.stream().map(dto -> fromDto(dto)).collect(Collectors.toList());
	}
	
	public List<D> fromEntity(List<E> entities){
		if(entities == null) return null;
		return entities.stream().map(entity -> fromEntity(entity)).collect(Collectors.toList());
	}
}

Observa que hemos agregado dos nuevos métodos, fromDto y fromEntity los cuales se encargan de convertir las listas aprovechando los método abstractos previamente definidos, de esta forma, en cuanto creemos una implementación de AbstractConverter tendremos de forma automática los método para convertir listas. Ahora veamos como quedaría el ejemplo de un servicio de consulta de todos los usuarios:

public List<UserDTO> findAllUsers() {
	List<User> users = userDAO.findAllUsers();
	UserConverter converter = new UserConverter();
	return converter.fromEntity(users);
}

Observemos que esta ves solo hace falta pasar la lista al Converter para que este se encargue de iterar la lista y crearnos una nueva con todos los datos convertidos.

Ya solo nos quedaría hacer un ejemplo de convertir una lista de DTO a Entity, pero creo que ya está de más, por lo que si tienes tiempo, puedes hacer una prueba tu mismo para que veas que funcionará de la misma forma.

Composición de convertidores

Otra de las ventajas de los convertidores es que podemos utilizar un Converter dentro de otro, por ejemplo, imagina que tienes una Entidad Invoice (factura) la cual tiene asociado al usuario que la creo:

package com.oscarblancarteblog.entity;

import java.util.List;

public class Invoice {
	
	private Long id;
	private User user;
	private List<Lines> lines;
}

No te voy a aburrir nuevamente con toda la explicación, así que solo nos enfocaremos en las novedades. Dicho esto, observar que entre todos los campos que puede tener una factura, tiene una referencia al Usuario, por lo que en el converter de la factura podríamos implementar nuevamente la lógica para convertir al usuario, pero eso sería estúpido, pues ya tenemos una clase que lo hace, por lo tanto, podríamos utilizar el converter del usuario dentro del converter de la factura:

package com.oscarblancarteblog.converter;

import com.oscarblancarteblog.entity.Invoice;
import com.oscarblancarteblog.entity.User;

public class InvoiceConverter extends AbstractConverter<Invoice, InvoiceDTO>{

	private final UserConverter userConverter = new UserConverter();
	
	@Override
	public Invoice fromDto(InvoiceDTO dto) {
		
		Invoice invoice = new Invoice();
		invoice.setUser(userConverter.fromDto(dto.getUser()));
		// ... other setters
		return invoice;
	}

	@Override
	public InvoiceDTO fromEntity(Invoice entity) {
		InvoiceDTO invoice = new InvoiceDTO();
		invoice.setUser(userConverter.fromEntity(entity.getUser()));
		// ... other setters
		return invoice;
	}
}

En este nuevo converter podemos ver que hemos instanciado a UserConverter y lo hemos utilizado para convertir el usuario en lugar de volver a implementar la lógica de conversión aquí.

Conclusiones

Hemos podido comprobar que los convertidores son de gran ayuda para evitar la repetición de código que puede se tedioso y propenso a errores, además, vemos que mediante la composición podemos reutilizar los convertidores para crear convertidores más avanzados.

Solo me resta mencionar un detalle importante, y es que debemos de tener cuidado que datos regresamos al cliente, ya que por ejemplo, regresar el password de nuestros usuarios a nuestros clientes puede ser una mala idea, así que podemos omitir este campo en el converter o eliminarlo de los objetos una vez que ha sido convertido, todo dependerá de que te funcione mejor.

Finalmente, recuerda que la clase base AbstractConverter es un punto de partida importante para implementar método genéricos para todos los convertidores, por lo que debemos aprovecharla si queremos agregar nuevos método en el futuro.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *