Como crear un microservicio o servicio web REST con Spring Boot (Parte 3)

En la primera parte de este post creamos la interfaz de un microservicio o servicio web REST usando Spring Boot y en la segunda parte agregamos la lógica de negocio y el acceso a datos, sin embargo el servicio tiene problemas de diseño que causan acoplamientos negativos, dificultan su evolución y las pruebas automatizadas. En esta última entrega corregiremos esos problemas y haremos uso de varias características avanzadas de Spring Boot y otras librerías.

Desacoplando a los clientes

La interfaz de servicio (ContactsApi) retorna objetos de tipo Contact que hemos marcado como entidades con la anotación @Entity; en otras palabras: nuestro servicio web está exponiendo a sus clientes, el modelo de datos tal como es. Esto implica que si hacemos un cambio en la estructura de base de datos, el cambio automáticamente se verá en la interfaz de servicio y afectará a los clientes del servicio web, por lo que decimos que existe un fuerte acoplamiento negativo entre los clientes y la interfaz del servicio web. Este es un antipatrón también conocido como Modelo del Dominio como REST. La forma de solucionarlo, ilustrada en el siguiente diagrama, es crear objetos independientes para la interfaz de servicio y objetos (entidades) diferentes para el modelo de datos; de esta manera podremos hacer cambios en el modelo de datos sin afectar obligatoriamente a los clientes.Lo malo de este enfoque es que tendremos que hacer un mapeo entre los campos de la entidad y los campos de los objetos que usemos en la interfaz de servicio web. Hay varias formas en la que podemos hacer este mapeo:

  1. Manualmente, escribiendo una clase auxiliar que haga el mapeo para que sea reutilizable
  2. Como parte de la lógica de los POJOs/DTOs de la capa de interfaz
  3. Utilizando una librería de mapeo de objetos como Dozer, ModelMapper o MapStruct .

Como este última parte del tutorial es más avanzada, usaremos Dozer y si quieres detalles, puedes solicitar un tutorial de Dozer en los comentarios.

El modelo de datos del API

Crea las clases ContactRequest y ContactResponse. Estas clases serán los POJOs que el servicio web recibirá y entregará a sus clientes, en lugar de la entidad Contact, por lo que su estructura es muy similar y puedes verla en los fuentes que están en nuestro repositorio en GitHub. Al evitar que la interfaz del servicio web entregue a sus clientes cualquiera de nuestras entidades, ya los hemos desacoplado de nuestro modelo de datos del servicio que es diferente al modelo de datos del API.

Este cambio implica que debemos cambiar la interfaz del servicio web (Contacts) para que reciba y entregue instancias de estas nuevas clases, así:

public ContactResponse updateOrSave(@RequestBody ContactRequest contactRequest)

Mapeando entre modelos de datos

También debemos agregar los mapeos correspondientes entre los DTO que se intercambian con el cliente (ContactResponse y ContactRequest) y las entidades (Contact) que recibe y entiende nuestra clase de lógica de negocio (ContactService). Para esto debemos agregar la librería Dozer al archivo pom.xml y configurar un bean de Dozer en una clase de configuración:

  
<dependency>
  <groupId>net.sf.dozer</groupId>
  <artifactId>dozer</artifactId>
  <version>5.5.1</version>
</dependency>
@Configuration // (1)
public class DozerMapper {
	@Bean  // (2)
	public Mapper beanMapper() {
		return new DozerBeanMapper();  // (3)
	}
}
  1. La anotación @Configuration indica a Spring que esta clase tiene métodos anotados con @Bean que puede procesar en tiempo de ejecución para producir beans que pueden ser inyectados por otros beans
  2. La anotación @Bean en un método indica que el valor que retorne el método quedará disponible como un bean en el contexto de Spring,, listo para ser inyectado en otro bean
  3. Creamos una instancia de DozerBeanMapper que podremos inyectar en nuestro RestController u otro bean de Spring. Por defecto, el nombre del nuevo bean será el mismo nombre del método que lo produce. El nombre se puede personalizar con el parámetro name.

El mapper de Dozer intentará asignar los valores de las propiedades de un bean a las propiedades del mismo nombre en otro bean encargándose automáticamente de la conversión de tipos. Dozer también permite mapeos avanzados o personalizados si los nombres de las propiedades no son los mismos (deja un comentario si deseas un tutorial sobre Dozer).

En nuestro caso las propiedades del DTO y del Entity se llaman igual, así que no requerimos especificar mapeos explícitamente y nuestra clase de interfaz (ContactsApi) queda así:

// Inyecta mapper de Dozer
@Autowired
Mapper mapper;
 
@RequestMapping(value="/contact", method=RequestMethod.POST)
public ContactResponse updateOrSave(@RequestBody ContactRequest contactRequest){
    // Mapeo request dto ==&amp;amp;amp;amp;amp;gt; entity
    Contact contact = mapper.map(contactRequest, Contact.class);
    
    // Invoca lógica de negocio
    Contact updatedContact = contactService.save(contact);
    
    // Mapeo entity ==&amp;amp;amp;amp;amp;gt; response dto
    ContactResponse contactResponse = mapper.map(updatedContact, ContactResponse.class); 
    
    return contactResponse;
}

Este código podría ser más breve pero deseo resaltar cada paso de manera independiente. Con esto hemos desacoplado los clientes de nuestro servicio de nuestro modelo de datos permitiendo su evolución independiente. Veamos ahora otras características de Spring Boot que facilitan el desarrollo de microservicios.

Validación automática de datos de entrada

Probablemente recuerdas de otros proyectos, algún controller con muchas instrucciones if para verificar que los datos de entrada no sean nulos, tengan un valor numérico en un rango específico, tengan una longitud de cadena determinada u otras validaciones. Con Spring y el API de JavaEE en el paquete javax.validation, no sólo podemos reducir este código sino además centralizarlo y simplificar el proceso.

Asumamos que requerimos las siguientes validaciones para el contacto que queremos crear:

  1. El nombre del contacto es requerido (no puede ser nulo)
  2. El nombre del contacto debe tener por lo menos 2 caracteres de longitud y máximo 30 caracteres
  3. El número de teléfono empieza con el símbolo «+» seguido de por lo menos un dígito y únicamente admite una secuencia de dígitos entre el 0 y el 9, sin espacios ni separadores

Estas restricciones se agregan mediante anotaciones a los campos de la clase que queremos verificar, en nuestro caso, ContactRequest, usando anotaciones del paquete javax.validation.constraint, el cual te recomendamos examinar para ver más validaciones útiles:

@NotNull(message="El nombre es requerido")
@Size(min=2, max=30, message="El nombre debe tener entre {min} y {max} caracteres")
private String firstName;

@Pattern(regexp="^\\+[0-9]*$", message="El número de telefono sólo puede tener dígitos iniciando con el símbolo +")

La validación automática la implementas agregando la anotación @Valid junto al parámetro que quieras validar, siempre y cuando la Clase respectiva tenga las anotaciones que acabamos de ver. Esto lo podemos hacer en el RestController:

public ContactResponse updateOrSave(@RequestBody @Valid ContactRequest contactRequest)

Este es un fragmento de una respuesta a una petición no válida:

{...
"defaultMessage": "El nombre debe tener entre 2 y 30 caracteres",
"objectName": "contactRequest",
"field": "firstName",
"rejectedValue": "",
"bindingFailure": false,
"code": "Size"
...}

Conectando con una base de datos externas

Hasta este momento, todos los registros que guardamos en base de datos mediante las peticiones POST han sido guardados en una base de datos en memoria que se borra cuando se finaliza la aplicación. Aunque esto es muy útil para las pruebas unitarias y durante la etapa de desarrollo… de poco sirve para una aplicación en producción, por eso vamos a ver como conectarse a una base de datos externa… manteniendo también la opción en memoria! Lo mejor de ambos mundos.

Perfiles y Ambientes

Una de mis funcionalidades favoritas de Spring es la posibilidad de mantener configuraciones diferentes, por ejemplo, para usar en ambientes diferentes y que se pueden seleccionar dinámicamente al iniciar la aplicación. De esta manera podemos usar la base de datos en memoria (H2) durante el desarrollo y una base de datos externa como MySQL durante las pruebas de integración, las pruebas de aceptación u otro ambiente como staging o producción.

La configuración que cambia entre ambientes se debe mantener en un archivo de propiedades para cada ambiente. Cada uno de estos archivos contiene una lista de propiedades que se conoce como perfil y tiene un nombre o selector asociado. Estas propiedades reemplazan a la configuración por defecto si se especifican.

Por defecto, Spring Boot se configura con una base de datos H2 en memoria y suponiendo que nos interesa cambiar esta configuración sólo en un ambiente de UAT (Pruebas de Aceptación) para conectarse a una base de datos MySQL, entonces debemos crear un archivo de propiedades con un selector «UAT» y las propiedades de conexión a base de datos que reemplacen la configuración por defecto.

Para hacerlo, ve al directorio «src/main/resources» y crea un archivo de nombre «application-uat.properties». El selector o nombre de perfil es el texto que está después del guión («-«) y antes del punto, en este caso «uat». Puede ser cualquier texto siempre que sea un nombre de archivo válido. En ese archivo, escribe lo siguiente:

spring.datasource.platform=mysql
spring.datasource.url=jdbc:mysql://localhost:3306/contacts_db
spring.datasource.username=contacts_user
spring.datasource.password=contacts_password

En este ejemplo asumimos que tienes una base de datos MySQL de nombre «contacts_db», instalada en la misma máquina en la que se ejecuta tu microservicio y que puedes acceder con el usuario «contacts_user» y password «contacts_password». Las propiedades se explican solas con excepción de spring.datasource.platform que sirve para indicarle a Spring cual base de datos usar en caso de que encuentre varios drivers JDBC en el classpath. Hasta el momento sólo tenemos el driver H2 que incluimos al crear el proyecto en la primera parte de este extenso post. Incluye la dependencia Maven para el driver de MySQL y ahora los tendrás ambos. Mira el pom.xml nuestro repositorio en GitHub.

Solo falta indicarle a Spring que por defecto utilice la base de datos H2, pues ahora debemos elegir entre H2 y MySQL. Para ello, en el archivo «application.properties» agrega la siguiente línea:

 spring.datasource.platform=h2 

Para seleccionar el perfil activo hay varias maneras que incluyen parámetros de la línea de comandos y variables de entorno. Usaremos ésta última: crea una variable de entorno del sistema operativo que se llame «SPRING_PROFILES_ACTIVE» con valor «uat» e inicia la aplicación. Si todo va bien deberías ver una línea como la siguiente en el log de inicio de la aplicación, indicando el perfil activo (uat):

INFO 20841 --- [ main] c.s.contacts.ContactsWsApplication : The following profiles are active: uat

Conclusión

Con las dos primeras partes de este post ya tendrías suficiente para tener un microservicio funcionando, pero el propósito de este blog es ayudarte a ir más allá y conseguir aplicaciones a prueba de balas. Por eso este ha sido un post muy extenso en el que hemos cubierto varios aspectos básicos, intermedios y avanzados que no son exclusivos de Spring Boot pero que en cualquier caso te ayudarán a construir un microservicio u otro tipo de aplicación basada en Spring, que sea modificable, fácil de mantener, fácil de configurar y que esté preparada para ser usada en pruebas automatizadas.

También tratamos algunos temas de manera superficial pero que merecen un post aparte. Aún hay cosas por mejorar como definir nuestras clase de lógica usando interfaces para facilitar el uso de mocks en pruebas automatizadas, pero volveremos a estos temas en futuros posts.

No olvides suscribirte a nuestras actualizaciones y seguirnos en Facebook.

Revisa las otras partes de este post:

«