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铆.

Curso de Java lambdas y Streams
Te invitamos a ver nuestro curso de Java Lambdas + Streams para aprender a programar con el nuevos paradigma funcional.

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.

12 thoughts to “Java Converter Pattern”

  1. Hola Tocayo, 隆Espl茅ndido D铆a!

    Primero que todo, muchas gracias por compartir una muy buena referencia de este patr贸n y la forma adecuada de implemntarla en Java.

    Segundo me gustar铆a si es posible compatiertas algo similar usando <>, revis茅 un poco s贸lo que por el momento lo que encontr茅 est谩 en Ingl茅s.

    Saludos Cordiales,
    脫scar O. Bravo M.

      1. Estoy intentando con un objeto Rol en lugar del enum, el objeto tiene 2 atributos, id y descripcion, cambie en el dto de User, List roles por private List roles (suponiendo que la consulta viene un objeto con la siguiente estructura).
        {
        “username”: “Mary”,
        “password”: “12345**”,
        “id”: “34567”,
        “roles”: [
        {
        “description”: “Rol 1”,
        “rolId”: “r1”
        },
        {
        “description”: “Rol 2”,
        “rolId”: “r2”
        }
        ]
        }
        En la clase que se de UserConverter en la asignaci贸n de setRoles con la expresi贸n lambda, me genera error, en esta linea
        user.setRoles(entity.getRoles().stream().map(rol -> entity.getRolList()).collect(Collectors.toList()));
        me dice que no es del mismo dato porque uno es de entidad y el otro de Dto
        驴C贸mo se podr铆a terminar de hacer esa conversi贸n?
        Tengo tambien una clase rolConverter, alguna idea ?

        1. Bueno, a simple vista no sabr铆a decirte, no conozco la estructura completa del proyecto pero te doy un consejo, si no dominas bien los Lamda expressions, es mejor hacerlo de la forma tradicional, se que los lamda se ven m谩s pro, pero al final, si lo haces bien, el resultado ser谩 el mismo 馃槈

          1. No es que sean m谩s pro, en rendimiento es mejor la programaci贸n funcional, ya lo solucione era crear un objeto del tipo que necesitaba dentro de la expresi贸n lambda con ello quedo el patr贸n implementado, bastante 煤til

          1. en user converter , antes de la lambda se tiene la asignacion de las otras propiedades del objeto , para asignar el objeto rol lo realice asi
            user.setRolList(entity.getRolList().stream().map(rol -> new RolConverterAbstract().fromEntity(rol)) .collect(Collectors.toList()));

Deja un comentario

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