Lambda Architecture

Una vez más voy a escribir un artículo donde resumo y hago un pequeño ejemplo de otro libro sobre arquitectura que he leído últimamente. Se trata del popular Big Data: Principles and Best Practices of Scalable Real-time Data Systems de Nathan Marz y James Warren. En este libro se explican los principios básicos de las arquitecturas Big data, la arquitectura Lambda y un ejemplo de cómo implementarla. La arquitectura Lambda es utilizada para resolver problemas de Big Data, lo que quiere decir, problemas donde tenemos que gestionar grandes volúmenes de datos de manera escalable. En los últimos años la cantidad de datos que gestionan las compañías han crecido exponencialmente con el uso de las aplicaciones móviles y los dispositivos inteligentes. Los sistemas tradicionales han quedado colapsados y las compañías se ven obligadas a migrarlos a arquitecturas Big Data como Lambda.

La arquitectura Lambda contiene las siguientes capas:

  • Batch layer: esta capa procesa una copia de lo que es llamado «master data set» que es la colección de datos que componen la fuente de verdad, lo que llamamos hechos y genera a partir de ellos, las batch views, que no son más preprocesamientos de posibles futuras consultas, para poder atender peticiones al sistema de manera más rápida. Por ejemplo si en una arquitectura tradicional se quisieran consultar todos los eventos sobre la venta de un producto específico durante un periodo de tiempo sería necesario hacer una consulta a la base de datos trayendolos todos los eventos y sumarlos, lo que puede ser muy costoso si el sistema es grande y guarda cientos de miles de eventos. Sin embargo con Lambda architecture generaríamos en esta capa una vista preprocesada contando todos los eventos para cada producto y periodo de tiempo, consiguiendo que lo mismo se obtenga con una única consulta a la base de datos tendría una latencia muy baja. Como es evidente, esta capa se llama batch porque se ejecuta en segundo plano, con la frecuencia con la que se considere necesario según los requisitos del sistema. Para general las vistas se hace uso del popular algoritmo MapReduce.
  • Speed layer: esta capa es la encargada de crear las speed views que no son más que la información preprocesada que no ha sido capaz de ser generada por la batch layer desde su última ejecución. Mientras que la batch layer funciona de manera asíncrona en modo batch, la speed layer procesa los eventos en tiempo real.
  • Serving layer: responde a las consultas que tiene el sistema combinando la información preprocesada en las batch and speed views, combinándola cuando es necesario. De la combinación de todas las capas conseguimos baja latencia para consultar muchos datos (batch layer) con información muy actualizada hasta el último momento (speed layer).

La arquitectura Lambda tiene una serie de propiedades que nos son muy útiles:

  • Tolerante a fallos humanos: esta propiedad la tenemos gracias a que las vistas son generadas de nuevo en cada ejecución de la batch layer, por lo que si se detecta un error en algún algoritmo, se puede corregir y en la siguiente ejecución tendremos vistas correctas (la batch layer puede estar configurada para funcionar de manera incremental, con perjuicio de esta propiedad).
  • Tiene baja latencia en lecturas y escrituras: gracias al algoritmo Mapreduce, la capa de batch es capaz de procesar grandes cantidades de datos y crear vistas con consultas preprocesadas que son muchísimo más rápidas de consultar que su equivalencia en un sistema tradicional. Esta capa tiene el problema de que no genera vistas hasta el momento actual, ya que requiere de un tiempo de procesamiento a partir del cual no procesa hechos nuevos hasta la siguiente ejecución. Este inconveniente lo resolvemos con la speed layer que está procesando todos los eventos en tiempo real, permitiendo que la serving layer provea de respuestas con baja latencia y muy actualizadas.
  • Escalabilidad: la escalabilidad es la capacidad de mantener un nivel de rendimiento cuando aumenta el número de peticiones, añadiendo más máquinas de manera lineal, de manera que pueda ser controlada (un sistema no escalable puede requerir un aumento exponencial de recursos para aumentar la cantidad de peticiones que consume llegado cierto punto). Dado que la arquitectura Lambda utiliza el algoritmo MapReduce cumple con este requisito.
  • Fácil mantenimiento: Lambda permite un mantenimiento más sencillo y predecible que un sistema tradicional, al poder medir por adelantado cuánto tarda la batch layer en crear sus vistas con el nivel de datos que tenga de cada momento, y permitiéndonos actuar por adelantado, añadiendo más máquinas si es necesario.
  • Fácil depuración: es más fácil encontrar un error al generar las vistas, que en un sistema tradicional, ya que tenemos intactos los hechos que sirvieron de base para generarlas y podemos reproducir su generación. Esto mismo no se podría hacer con un sistema tradicional con entidades mutables en una base de datos, porque la sucesión de cambios haría imposible saber cómo era anteriormente la entidad y detectar en que punto del tiempo se produjo el error.

He preparado un ejemplo donde implemento de manera parcial la arquitectura Lambda  (batch y serving layer). El ejemplo consiste en un sistema para procesar consultas sobre la venta de productos de un comercio. Este sistema responde a dos tipos de consultas: número de productos vendidos por día y número de productos de una categoría vendidos por día. Para este ejemplo he utilizado Java y Sprint Boot como base de los proyectos, Spark para implementar el algoritmo MapReduce de la batch layer, Avro para serializar los hechos del master data set y Mongodb con base de datos No Sql. Podéis encontrar el ejemplo en mi GitHub.

El ejemplo contiene 3 proyectos.

  • sales-rest-api: esta es una sencilla API Rest que recibe mensajes por cada venta indicando el producto, la categoría de este y la fecha de compra. Esta información se almacena en el master data set que está contenido en ficheros con formato Avro.
  • sales-batch-service: este proyecto lee el master data set del fichero con formato Avro y genara las batch views usando Spark que son almacenadas en Mondodb. Este es un fragmento de código donde se implementa MapReduce:
 private JavaRDD getSaleJavaRDD() throws IOException {
        val sparkConf = new SparkConf().setMaster(LOCAL).setAppName(APP_NAME);
        val sc = new JavaSparkContext(sparkConf);
        val sales = saleRepository.getSales();
        return sc.parallelize(sales);
    }

    private List<Tuple2> countBy(JavaRDD perJavaRDD, Function function0) {
        val ones = perJavaRDD.mapToPair((PairFunction)
                s -> new Tuple2(new ProductView(function0.call(s), s.getSaleDate()), 1));
        val counts = ones.reduceByKey((Function2)
                (i1, i2) -> i1 + i2);
        return counts.collect();
    }

    private List map(List<Tuple2> nProduct, ViewType viewType) {
        return nProduct.stream().map(tuple -> {
            val productsByDayView = tuple._1;
            productsByDayView.setUnits(tuple._2);
            productsByDayView.setViewType(viewType);
            return productsByDayView;
        }).collect(Collectors.toList());
    }
  • sales-serving-service: es otra sencilla Api Rest que responde a las consultas utilizando las vistas preprocesada previamente por la batch layer y almacenadas en Mongodb.

Para completar el ejemplo faltaría implementar la speed layer y modificar la serving layer para que utilice sus vistas.

Resumiendo creo que la arquitectura Lambda es muy interesante y recomiendo la lectura de Big Data: Principles and best practices of scalable realtime data systems para tener una visión más completa de ella.

Clean architecture

Siguiendo con artículos sobre lecturas que he realizado últimamente, hoy voy a presentar un ejemplo de código basado en la lectura de un estupendo libro del popular Uncle Bob (Robert C. Martin). Este libro no es ni más ni menos que Clean Architecture, obra donde su autor nos propone una arquitectura que bebe de otras arquitecturas que han surgido en los últimos años y cuya lectura resulta muy amena, ya que utiliza un estilo que recuerda más a una novela que a un texto puramente técnico.

No voy a resumir el libro, el cual desglosa un montón de buenas prácticas ilustrándolas con ejemplos de vivencias personales del autor, que lleva desde los años 70 desarrollando código. Invito a quien quiera profundizar en Clean Architecture a que lea el libro para tener una idea más  detallada de la propuesta de Uncle Bob. Simplemente me voy a limitar a explicar de manera breve las capas en las que se basa la arquitectura y a mostrar mi implementación de ejemplo. Estas capas son:

  • Capa de entidad: las entidades contienen las reglas puras de negocio.
  • Capa de casos de uso: los casos de uso contienen las reglas de negocio concreta para la aplicación.
  • Capa de adaptadores de interfaz: esta capa se encarga de transformar los datos que provienen de dependencias externas como bases de datos y Frameworks web a la manera más conveniente para las entidades y casos de uso.
  • Capa de Frameworks y drivers: capa que contiene las dependencias externas y donde escribimos muy poco código.

Entre estas capas hay una regla de oro que consiste en que no hay dependencias de las capas superiores en las inferiores, por ejemplo la capa de entidad no puede tener dependencia de la capa de casos de uso pero si lo contrario. De esta manera podemos tener la lógica desacoplado entre capas y podemos probarla de manera independiente, creando mocks de las dependencias. Si una capa superior necesita de una dependencia de una capa inferior se define un interfaz para obtenerla de manera que quede completamente desacoplada.

Para realizar un ejemplo sobre lo que yo he entendido de esta arquitectura (como yo la he asimilado, no digo que sea una implementación perfecta de lo que tenía en mente Uncle Bob), he elegido un sencillo desarrollo que consiste en el verbo POST de un CRUD de API Rest de una entidad extremadamente sencilla de usuario. El código está escrito en Java 8 con Sprint Boot.  Voy a explicar cómo he realizado el código que podéis encontrar en mi github.

    • Primero he creado la entidad User que está en el paquete entity. La entidad es muy sencilla para no complicar el ejemplo :
package com.coconutcode.user.entity;

import lombok.Getter;

import java.util.Optional;

@Getter
public class User {
    private String username;

    public User(String username) throws UsernameNotIncluded {
        if(Optional.ofNullable(username).isPresent()) {
            this.username = username;
        } else {
            throw new UsernameNotIncluded();
        }
    }
}

    • Después la clase que contiene el caso de uso de crear un usuario en el paquete usecases, un interfaz para definir la dependencia que realizará la persistencia del nuevo usuario (CreateUser) y otro para buscar si un usuario ya existe con ese nombre (GetUser) Nótese que al crear estos interfaces desacoplamos el caso de uso de la capa de persistencia, ya que en esta capa no hay ningún tipo de detalle de cómo se realizarán las consultas a la base de datos o con que Framework.
package com.coconutcode.user.usecases;

import com.coconutcode.user.entity.User;
import lombok.AllArgsConstructor;
import java.util.Optional;

@AllArgsConstructor
public class CreateUserUseCase {
    private CreateUser createUser;

    private GetUser getUser;

    public User createUser(User user) throws MandatoryValueNotIncludedException {
        if(userIncluded(user)) {
            if(usernameAlreadyUsed(user)) {
                throw new MandatoryValueNotIncludedException("Username already exists: " + user.getUsername());
            } else {
                return createUser.createUser(user);
            }
        } else {
            throw new MandatoryValueNotIncludedException("User not included");
        }
    }

    private boolean usernameAlreadyUsed(User user) {
        return getUser.getUser(user.getUsername()).isPresent();
    }

    private boolean userIncluded(User user) {
        return Optional.ofNullable(user).isPresent();
    }
}

    • Después la clase que hace de adaptador con el Framework de base de datos que contiene el detalle sobre cómo persistir los datos (implementa los interfaces CreateUser y GetUser) y la clase que hace de adaptador con el Framework web que tiene el detalle de cómo presentar la información vía API Rest. Todo esto está en el paquete adapter:

package com.coconutcode.user.adapter.persistence;

import com.coconutcode.user.entity.User;
import com.coconutcode.user.usecases.CreateUser;
import com.coconutcode.user.usecases.GetUser;
import com.coconutcode.user.external.database.UserRepository;
import lombok.val;

import java.util.Optional;

public class UserPersistenceAdapter implements CreateUser, GetUser {
    private final UserRepository userRepository;

    public UserPersistenceAdapter(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public User createUser(User user) {
        val newUser = userRepository.save(new com.coconutcode.user.adapter.persistence.model.UserData(user.getUsername()));
        return new User(newUser.getUsername());
    }

    @Override
    public Optional getUser(String username) {
        val optionalUser = userRepository.findByUsername(username);
        return optionalUser.map(user -> new User(user.getUsername()));
    }
}
package com.coconutcode.user.adapter.presenter;

import com.coconutcode.user.entity.User;
import com.coconutcode.user.usecases.CreateUserUseCase;
import com.coconutcode.user.adapter.presenter.model.UserView;

public class UserPresenterAdapter {
    private final CreateUserUseCase createUserUseCase;

    public UserPresenterAdapter(CreateUserUseCase createUserUseCase){
        this.createUserUseCase = createUserUseCase;
    }

    public UserView createUser(UserView user) {
        return new UserView(createUserUseCase.createUser(new User(user.getUsername())));
    }
}

    • Por último en la capa de framework y drivers cuyo paquete he llamado external que contiene los detalles concretos de los frameworks. Al tener desacoplados los detalles de implementación podriamos cambiar por ejemplo el tipo de base de datos que se utiliza sin afectar a los casos de uso y a las entidades:
package com.coconutcode.user.external.database;

import com.coconutcode.user.adapter.persistence.model.UserData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public interface UserRepository extends JpaRepository{
    Optional findByUsername(String username);
}

package com.coconutcode.user.external.rest;

import com.coconutcode.user.adapter.presenter.UserPresenterAdapter;
import com.coconutcode.user.adapter.presenter.model.UserView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserRestController {
    private static final String USER_PATH = "/user";

    @Autowired
    private UserPresenterAdapter userPresenterAdapter;

    @RequestMapping(value = USER_PATH, method = RequestMethod.POST)
    public ResponseEntity create(@RequestBody UserView user) {
        try {
            return createOkResponse(userPresenterAdapter.createUser(user));
        } catch (Exception exception){
            return createBadRequestResponse(exception);
        }
    }

    private ResponseEntity createBadRequestResponse(Exception exception) {
        return new ResponseEntity(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    private ResponseEntity createOkResponse(@RequestBody UserView userView) {
        return new ResponseEntity(userView, HttpStatus.OK);
    }
}

Como podéis ver en github cada capa tiene pruebas unitarias donde las otras capas están mockeadas. Se podrían añadir más casos de uso sin necesidad de modificar el existente y diferentes adaptadores para cubrir otros requisitos funcionales sin afectar a casos de uso y entidades que contienen la lógica de negocio importante.

Resumiendo, en mi opinión, esta arquitectura nos permite realizar una organización mucho más racional y siguiéndola conseguimos un acoplamiento bajo entre paquetes, lo que es interesante porque generalmente estamos centrados en tener acoplamiento bajo a nivel de clase pero descuidamos la organización del código a un nivel superior. También creo que esta arqutectura asi como otras que son populadres en los últimos años (DDD, hexagonal, etc) pone el foco en dejar el modelo libre de dependencias externas de tal manera que pueda contener la lógica importante para la aplicación y pueda ser probado bien unitariamente.

Peopleware

Hoy me gustaría escribir mis impresiones sobre un libro que he tenido la oportunidad de leer estos días y que me ha causado una gran impresión. Se trata de la tercera edición de Peopleware de Tom DeMarco y Timothy Lister (que comparte apellido con el mítico dirigente del Quinto Regimiento, pero no tiene nada que ver con él). Esta obra, habla sobre lo que rodea al software sin incluir una sola línea de código. Tiene como tesis principal una interesante idea: los mayores problemas del software no son técnicos, son problemas relacionados con el trato, la gestión, la interacción, los entornos de trabajo en los que participamos las personas, o dicho de otro modo, lo mayores problemas del desarrollo del software son de naturaleza humana más que técnicos. Esta idea es desarrollada a lo largo de 39 capítulos. El libro describe un fenómeno por el cual la mayoría de los desarrolladores vivimos una ilusión por la que creemos que estamos en la brecha de creación de nueva tecnología, mientras en realidad, muy poca gente trabaja creando tecnología puntera y el 99% de la gente que escribimos software utilizamos tecnología hecha por terceros (librerías, frameworks, lenguajes, hardware, paradigmas, patrones de diseño, etc) para hacer aplicaciones de uso diario de todo tipo. A esto le que llaman en inglés High-Tech Illusion.

Si eres un desarrollador con unos cuantos años de experiencia, que has tenido la oportunidad de trabajar en varias en empresas, la lectura del libro te generará un sin fin de sensaciones de haber vivido las situaciones que se describen y de haber pensado anteriormente muchas de las conclusiones a las que se llegan.

Uno de los aspectos que se critica en el libro es la manera en la que se organizan las oficinas de un tiempo a esta parte. Buscando un aire de modernidad se han eliminado los despachos y las puertas, y todos convivimos en oficinas diáfanas donde generalmente hay más ruido de lo que sería conveniente para poder concentrarse y donde es difícil encontrar un espacio para tener una reunión. El libro pone un ejemplo de algo que yo he visto en muchas ocasiones: personas trabajando en la cocina de la empresa para tener algo de tranquilidad. Los autores del libro llevan décadas realizando ejercicios en empresas para medir la productividad donde han encontrado evidencias de una menor productividad si el espacio de trabajo poco adecuado para la concentración.

El liderazgo dentro de los equipos es otro elemento al que los autores prestan atención en el libro. Se realiza una crítica a super liderazgos donde el jefe de equipo quiere tomar cada decisión y no deja que ningún detalle no pase por el mismo, o liderazgos basados en hacer que los elementos del equipo o equipos de la misma empresa luchen entre ellos. Otro tipo de liderazgo descrito como nocivo es aquel que se basa en cargar de trabajo de manera extrema a los miembros del equipo, consiguiendo que solo estén contentos los «workaholics», hasta el día que se hartan y se marchan de la empresa también. Eso me hace recordar un jefe que yo tuve cuando trabajaba en la industria de los aviones hace unos años, que nos mentía sobre las fechas de entrega de la siguiente versión, diciéndonos que la fecha era unos días antes de lo que realmente era y forzandonos a echar muchas horas para llegar a esa fecha ficticia. Recomiendan más un líder cuya función principal es facilitar que el equipo que pueda trabajar tranquilo, eliminando las trabas que vayan surgiendo, y confiando en lo que puedan hacer aunque eso conlleve que comentan errores. También hablan de la importancia del capital humano y del tremendo coste que tiene perder a un trabajador frente al coste que podría tener mantenerlo, coste que a menudo es despreciado por las empresas.

Con respecto a los equipos, lanzan la idea de que un equipo bien engrasado que se conoce mucho y tiene buena sintonía, puede alcanzar cotas de productividad altísimas. Conseguir esto es muy complicado pero muchas veces se consigue involucrando a los elementos del equipo para conseguir una alta calidad en el software que producen. Esto entra en conflicto con los objetivos de las empresas que a veces les basta con un nivel de calidad bajo (ya que el mercado esta acostumbrado a que el software tenga muchos fallos) pero necesitan una velocidad alta para llegar al mercado rápido. De llegar una armonía entre ambos aspectos puede depender el éxito de un equipo. También se apunta que un exceso de burocracia en una empresa puede matar a un equipo. Y  la separación física de un equipo de manera generalizada también. Quien haya trabajado en remoto empatizará con esto último…

El libro está plagado de muchas más ideas y ejemplos concretos. Lo que he citado son algunos de los que han quedado marcados en mi cabeza, supongo que influenciado por mis propias experiencias personales. Es un libro altamente recomendable, que aconsejo a cualquiera que se dedique a hacer software. Viene a reforzar una idea que me viene rondando últimamente y que seguramente es una obviedad: la gestión de todo lo que rodea a hacer software (algo grande que se venda y lo use gente, no una POC) es tremendamente difícil.

Eureka

Durante los pasados meses, Fon, mi empresa, ha organizado una experiencia de comunidades tecnológicas, que consistía en asignarnos un par de horas semanales para trabajar en un proyecto en grupo para investigar sobre tecnologías nuevas. En la comunidad a la que pertenecía, la comunidad de Backend decidimos hacer una prueba de concepto con los productos del stack de Netflix. El siguiente artículo intenta explicar lo que aprendí de uno de ellos, Eureka.

charla

Eureka

¿Que soluciona?

Eureka es un servicio basado en REST que se utiliza para el descubrimiento y registro automático de servicios, lo que facilita las tareas de escalabilidad, balanceo de carga y tolerancia fallos en una arquitectura de microservicios. Netflix lo utiliza en AWS cloud, que es donde están desplegados sus microservicios. Eureka tiene dos componentes cliente y servidor.

¿Como funciona?

Explicado a grandes rasgos, tendríamos un servidor Eureka en nuestra arquitectura de microservicios y cada microservicio sería cliente de este servidor. Al arrancar cualquiera de los microservicios, se comunicaría vía REST con el servidor diciéndole donde está (IP y puerto), quién es (definido en la propiedad spring.application.name del fichero aplication.yml), donde esta la url donde consultar su estado interno (endpoint health de actuator por defecto), donde está su página de home, etc. A partir de ese momento cada microservicio se comunica con Eureka indicando que sigue vivo, con lo que la documentación de Eureka llama «heartbeats» que se traduciría como latidos. Por defecto los heartbeats se envían cada 30 segundos. Si el servidor Eureka no recibe está comunicación durante un tiempo configurable por parte de una instancia de microservicio (90 segundos por defecto), esta se elimina de la lista de instancias disponibles. Los microservicios utilizan la lista con información del resto de microservicios para comunicarse entre ellos, a través del balanceador de carga software llamado Ribbon, el cual está también basado en la tecnología desarrollada por Netflix. Tanto servidor como clientes tienen una caché con la información del resto de servicios disponibles. Un microservicio no se muestra como disponible hasta que su información está en las caches de servidor y clientes. Es posible definir información adicional para ser compartida entre los microservicios añadiéndola mediante configuración. Eureka como servidor incluye un panel de monitorización con información útil como por ejemplo la lista de servicios registrados en el servidor, cuales son los servidores réplica sí los hay, etc.

Modos de configurar un servidor Eureka

Un servidor Eureka se puede configurar para existir solo en nuestra arquitectura (standalone mode) o para funcionar con otras instancias de Eureka (peer awareness). En este segundo modo, las distintos servidores Eureka se registran entre ellos replicando su información, con lo que consigue mayor disponibilidad y resistencia a fallos. En el ejemplo que mostraré en la siguiente sección, se explica como configurar dos instancias de Eureka en modo peer awareness. Actualmente, Peer awareness es la manera de configurar Eureka más común en proyectos en producción. Cuando un servidor comienza a recibir información sobre los microservicios, esta información es replicada en el resto de servidores de los que tiene conocimiento. Si esta coordinación falla, la información es enviada en el siguiente heartbeat. Los servidores Eureka utilizan el mismo mecanismo para hablar entre ellos que cuando hay una comunicación Eureka cliente-servidor. Cada microservicio incluye en su fichero de properties la lista de servidores Eureka con los que se puede comunicar y se conectan al primer servidor de la lista por defecto.

Modo self-preservation

Eureka puede entrar en modo self-preservation para evitar eliminar instancias registradas cuando se experimentan fallos de red pero los microservicios gozan de buena salud. Eureka entra en este modo, si los heartbeats durante los últimos 15 minutos (valor por defecto), son menos que los heartbeats esperados, valor que se definen en un límite configurable. La fórmula sería para calcular este límite es:

  • Número de instancias * Número de heatbeats esperados por minuto * porcentaje esperado

Si por ejemplo de servicios que tenemos es 4, y se esperan 2 heatbeats por minuto (1 cada 30 segundos) y el porcentaje esperado es 0.85 tendríamos: 4 * 2 * 0.85 = 7 heatbeats por minuto.

Para definir estos valores entran en juego las siguientes propiedades de la configuración de Eureka:

  • eureka.instance.leaseRenewalIntervalInSeconds: define cada cuanto se envía los heartbeats al servidor Eureka
  • eureka.server.renewalPercentThreshold: defíne el límite de instancias que tienen que renovar su información por debajo del cual se entra en modo self preservation.
  • eureka.server.renewalThresholdUpdateIntervalMs: cada cuanto tiempo se calcula si Eureka tiene que entrar en self-preservation mode.

Ejemplo

Lo más sencillo para entender como funciona Eureka es ver un ejemplo. Podéis encontrar el código del ejemplo en github. En el ejemplo vamos a crear dos proyectos SpringBoot que serán servidores Eureka en modo peer awareness y un cliente Eureka. Este es un ejemplo pensado para ser ejecutado en una máquina, para desplegar Eureka en AWS se necesita configuración extra que podéis encontrar aquí. Para crear los proyectos recomiendo usar spring initializr que es una web que nos permite generar proyectos SpringBoot definiendo previsamente sus dependencias en maven o gradle:

springinitializr

Para el primero de los servidores necesitamos crear un proyecto SpringBoot con la dependencia del starter de Eureka server. Después necesitamos añadir la anotación @EnableEurekaServer en el método main de la clase Application.

package com.fon.community;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Y añadir la siguiente configuración en el fichero de propiedades application.yml:

server:
  port: 8761

spring:
  application:
    name: eureka-peer

eureka:
  instance:
    hostname: peer1
  client:
    serviceUrl:
      defaultZone: http://user:password@peer2:8762/eureka/

security:
  basic:
    enabled: true
  user:
    name: user
    password: password

Está configuración contiene entre otras cosas, donde se va encontrar la réplica del servidor (el otro servidor que veremos a continuación), definida mediante la propiedad eureka.client.serviceUrl.defaultZone. Adicionalmente vamos a securizar el servidor, esto se hace definiendo como true la propiedad security.basic.enable y definiendo las propiedades username y password de security.basic. Para crear el otro servidor tenemos que hacer exactamente lo mismo pero definiendo otro puerto, hostname y como servidor replica indicar el primer servidor que hemos creado.

server:
  port: 8762

spring:
  application:
    name: eureka-peer

eureka:
  instance:
    hostname: peer2
  client:
    serviceUrl:
      defaultZone: http://user:password@peer1:8761/eureka/

security:
  basic:
    enabled: true
  user:
    name: user
    password: password

Para que el ejemplo funcione hay que añadir estas líneas al fichero /etc/hosts

127.0.0.1       peer1
127.0.0.1       peer2

Por último debemos crear otro proyecto que contendrá un cliente, que tendrá una sencilla API REST. Cuando lo arranquemos se registrará en ambos servidores Eureka. Requiere incluir la dependencia con el starter de Eureka. La clase Application requiere la anotación @EnableDiscoveryClient. También hemos añadido un sencillo endpoint en la ruta «/hello»:

package com.fon.serverclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@EnableDiscoveryClient
@SpringBootApplication
public class Application {

   public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
   }

   @RestController
   class HelloController {
      @RequestMapping("/hello")
      public String serviceInstancesByApplicationName() {
         return "HelloWorld!";
      }
   }
}

Y añadir la siguiente configuración en el fichero de propiedades application.yml:

server:
  port: 0

spring:
  application:
    name: server-client

eureka:
  client:
    serviceUrl:
      defaultZone: http://user:password@peer1:8761/eureka/, http://user:password@peer2:8761/eureka/

Por último, arrancando los 3 proyectos y accediendo al panel de Eureka de los servidores (http://localhost:8761 y http://localhost:8762) podremos ver que los cada servidor tiene como replica al otro servidor y además el cliente está registrado. Para acceder a los paneles son requeridas las credenciales que hemos definido en la configuración. Esta información no se mostrará de manera inmediata, hay que esperar a que los distintos elementos se registren y descubran entre si.

EurekaServer1

EurekaServer2

Buenas prácticas

Peer awareness

Es recomendable utilizar Eureka en modo peer awareness en lugar de standalone en entornos de alta disponibilidad. Funcionando en este modo los clientes se comunican con el primer servidor incluido en la lista de servidores Eureka de su configuración. Si este servidor deja de estar disponible pasan a comunicarse con el siguiente en la lista y si más tarde se recupera, vuelven al primero. Adicionalmente cada cliente contiene una caché con la información acerca del resto de instancias que continua disponible, si hay fallos de comunicación con el servidor Eureka. Ambos mecanismos hacen que Eureka sea muy robusto cuando funciona utilizando varias replicas.

Health check

Por defecto, Eureka usa los heartbeats para saber si el cliente está activo, pero no conoce su estado interno. Eureka se puede configurar para enviar el estado de la aplicación determinado por la librería actuator. Para hacer esto hay que aplicar la siguiente configuración:

eureka:
  client:
    healthcheck:
      enabled: true

Comunicación entre servicios

Es recomendable utilizar Feign para comunicaciones entre microservicios. Feign es otra librería de Netflix que nos permite declarar clientes REST de forma declarativa.

Entender como configurar Eureka

Eureka es altamente configurable y y es conveniente entender como funciona en detalle, si se va utilizar en un proyecto en producción. Para ampliar información sobre cómo configurar Eureka, he encontrado muy útil el siguiente artículo donde el autor explica en detalle las principales propiedades de configuración de Eureka.

JBCNConf2017

El pasado 19 de junio tuve la suerte de poder atender al JBCNConf  la conferencia sobre Java que se organiza en Barcelona de unos años para aquí. Acudí allí enviado Fon, que es la empresa donde trabajo para dar cuenta de lo que allí se contó. El evento estuvo muy bien organizado, con ponentes que tenían mucho nivel y temas interesantes del mundo «Java». La mayoría de las charlas se englobaron en los temas de arquitecturas orientadas a eventos (Event driven, CQRS, etc), programación reactiva, principios del desarrollo de micro servicios y las novedades de Java 9. Parece que estos son y van a ser los temas más de moda en los meetups de Java en los próximos meses. Dentro de las charlas que atendí, las que más me gustaron fueron:

Java Libraries You Can’t Afford to Miss de Andrés Almiral

 
En esta charla, Andres Almiral explicó en tiempo record, una lista de librerías que el considera muy útiles para cualquier desarrollador Java.
IMG_20170619_111151

Varias de las librerías que explicó fueron algunas muy conocidas como Lombok, Mockito, Jackson o Guava. Otras librerías interesantes que comentó fueron:

  • Retrofit(http://square.github.io/retrofit/): permite crear clientes de apis rest declarando un interface, de manera muy similar a como lo hace Feign de Netflix, pero utilizando un builder para instanciarlo.
  • Mbassador(https://java.libhunt.com/project/mbassador): es una librería que implementa el patrón observable de una manera muy eficiente y sencilla de usar.
  • Jdeferred(https://github.com/jdeferred/jdeferred): librería que nos permite hacer promesas para tratar con acciones asíncronas de manera similar a como lo hace angular 2.
  • Junitparams(https://github.com/Pragmatists/JUnitParams): permite parametrizar pruebas unitarias con lo que se puede evitar repetir mucho código.
  • Spock(http://spockframework.org): es un Framework de Groovy que nos permite escribir pruebas de una manera muy legible cercana al BDD. Vimos ejemplos de clases Java con pruebas unitarias hechas con Spock en diferentes charlas, lo que indica que esta combinación es cada vez más usada. Creo que sería interesante hacer alguna prueba de concepto en la comunidad backend, reescribiendo algunas pruebas unitarias usando Spock para ver las ventajas y desventajas de usarlo.
  • Awaitility(https://github.com/awaitility/awaitility): está librería nos facilita la creación de pruebas cuando tenemos sistemas asíncronos, definiendo por ejemplo asserts configurados para tener una respuesta en un determinado timeout o fallar en otro caso.
  • Wiremock(http://wiremock.org): nos permite hacer mocks o stubs de apis rest que no tenemos disponibles, por ejemplo porque estamos en el entorno de pruebas. Aquí hay un tutorial donde está bien explicado http://www.baeldung.com/introduction-to-wiremock.
He encontrado la charla aquí, es de otro evento pero cuenta lo mismo. Todavía no están disponibles los vídeos con las charlas, pero supongo que en algún momento los publicarán porque las grabaron todas y las del año pasado están publicadas.

Real World Java 9 de Trisha Gee

 
Esta charla fue bastante interesante. La ponente, desarrolladora en Jetbrains (los autores de Intellij), comentó las novedades de Java 9 mostrando y escribiendo código en directo. Explicó que Java 9 es más eficiente en varios aspectos, ya que implementa mejoras en la gestión de la memoria, gestión del hardware, mejor performance en general, mejor documentación, etc.
IMG_20170619_121341

Solo por esto ya merecería la pena usarlo. También recorrió cada una de las grandes features de Java 9:
  • Jigsaw: funcionalidad que mejora la modulariad y encapsulación del código. A partir de Java 9, si utilizamos esta funcionalidad(no es obligatorio por temas de compatibilidad con versiones anteriores) tendremos que añadir en cada módulo un fichero llamado module-info.java donde definimos el nombre del módulo, que clases o paquetes son accesibles desde fuera de nuestro modulo y cuáles se requieren. Parece que va ser un poco lioso al principio y Trisha recomendaba utilizarlo en proyectos nuevos más que hacerlo en proyectos ya muy avanzados. Trisha hizo ejemplos utilizando Intellij y la buena noticia es que este IDE tiene una buena integración y va escribir este fichero casi por si mismo, siguiendo indicaciones nuestras al resolver fallos de compilación relacionados con la definición del fichero module-info.java. Por ejemplo si no tenemos acceso a un paquete de una librería que tenemos incluida en gradle, Intellij va ser capaz de añadir la línea importándolo al resolver el fallo de compilación resultante de no tenerlo.
  • Nuevos métodos factoría para inicializar colecciones. Comentó que en su opinión esta va ser la funcionalidad más utilizada. Vamos a tener nuevos métodos para inicializar listas, mapas, etc, de manera menos tediosa que ahora. Por ejemplo un mapa se va poder inicializar escribiendo: Map.of(«Hello», 1, «World», 2).
IMG_20170619_125439

  • Java 9 incluye nuevos métodos e interfaces para hacer programación reactiva dentro de Java SE 9 Reactive Streams API.
También he encontrado el vídeo de esta charla en otro evento.

When, why and how to CQRS de Sebastian Daschner

 
Sebastian explicó los principios del patrón command query responsibility segregation (separar lecturas y escrituras, comunicación entre servicios solo por eventos, etc) y mostró una demo con un pequeño ejemplo, implementando 3 servicios siguiendo este patrón. Esta charla fue interesante porque a pesar de que es un tema muy de moda, del que se habla mucho, es bastante complicado encontrar ejemplos claros, y su ejemplo fue muy sencillo y fácil de entender, poniendo un poco de luz en el tema. La charla también circula en por Internet, si estáis interesados en CQRS os la recomiendo https://www.youtube.com/watch?v=iK8x1mUPwCE . El código de su ejemplo esta aquí https://github.com/sdaschner/scalable-coffee-shop
IMG_20170619_143347

Star Trek: BDD Enterprise de Tomasz Dubikowski

Esta charla nos sorprendió porque parecía que iba a ser peor de lo que fue en un primer momento. La presentación consistió en un recorrido por la experiencia laboral de Tomasz lidiando con proyectos donde se intentó seguir la metodología Behaviour driven development (BDD) y como en su opinión no es una práctica muy viable por lo complejo de las relaciones entre los departamentos de desarrollo y negocio. Como conclusión Tomasz recomendaba hacer las pruebas unitarios utilizando Spock, que como explicaba antes es un Framework de Groovy que tiene una sintaxis muy cercana al BDD. Decía que era una solución de compromiso, que sin llegar a ser BDD puro, conseguía pruebas legibles para gente no técnica. Esta charla también circula por Internet pero solo en polaco…

The Long Road de Sandro Mancuso

Para cerrar el evento, Sandro Mancuso autor de The Software Craftsman, dio una charla no técnica acerca de como enfocar el oficio de desarrollador, entrando en temas como cuando cambiar de trabajo, que empresas evitar, etc. Fue muy interesante, de hecho arrancó varios aplausos espontáneos y hubo algún momento curiosos, como cuando preguntó que cuales de nuestras empresas estaba buscando gente y prácticamente toda la sala  levanto la mano. Está charla está en Internet porque ya la había dado en otro evento, aquí está el enlace.

Taller de Test Refactoring de Wlodek Krakowski

El último día teníamos opción de escoger dos talleres prácticos de entre ocho y el primero al que acudimos fue el impartido por Wlodek Krakowski, con la temática de refactoring. El ponente es team leader y profersor en trainings sobre refactoring y demostró ser un auténtico maestro en la materia. 20 minutos después de empezar el taller nadie en la sala estaba escribiendo código, ya que era imposible seguir todos los cambios que estaba haciendo mediantes atajos de teclados de Intellij a toda velocidad, en una muestra de auténtico virtuosismo con este IDE. El refactoring que hizo (podéis ver el código aquí https://github.com/wlodekkr/tests-refactoring) consistió en refactorizar una típica clase con pruebas de una API rest hechas usando MockMvc. Básicamente aplicó el patrón builder a todo lo que tenía más de dos líneas de código y simplificó las pruebas muchísimo.
IMG_20170621_093434
En resumen, ha sido un evento muy recomendable y agradezco a la organización el esfuerzo y a mi empresa Fon hace el esfuerzo de mandarme.

Connascence

El otro día acudí a una interesante charla de Alfredo Casado @alfredocasado sobre Connascence que organizó el meetup de Software Craftsmanship Madrid. La charla muy bien llevada por Alfredo, estuvo llena de ejemplos reales de código y de consejos prácticos. Cómo he detectado que este término es desconocido para mucha gente, he decidido escribir este artículo resumiendo la charla de Alfredo aunque utilizando otros ejemplos de código que he ido encontrando.

¿Que es connascence?

Connascence es una métrica creada por Meilir Page-Jones en su libro What Every Programmer Should Know about Object-Oriented Design la cual nos permite hablar de la dependencia entre componentes software. Dos componentes software tienen una relación de connascence si para cambiar uno de ellos es necesario cambiar el otro para que su funcionamiento siga siendo correcto. Los términos connascence y acoplamiento son muy similares, hasta el punto de que Kent Beck escribió en Twitter que para él tienen la misma definición.
Connascence nos dota de un vocabulario para poder hablar de relaciones de dependencia entre componentes software, pudiendo hablar de diseño de una manera más avanzada. Conocer los tipos de connascence nos puede ayudar a mejorar nuestro código, ya que nos ayuda a reconocer el acoplamiento y nos da pistas sobre la dirección hacia dónde avanzar para disminuirlo.
Una relación connascence puede tener diferente fuerza, grado y localización.
– Será más fuerte cuando más número de cambios requiera modificar los elementos implicados, o dicho de otra manera cuanto más complejo es cambiar el código más fuerte es la relación de connascence.
– El grado está relacionado con la intensidad del impacto que requiere un cambio. Es menor el grado si afecta a dos métodos que si afecta a cientos.
– La localización depende de la distancia entre los componentes sofware que tiene una relación de connascence, si son del mismo método, clase, contexto, etc. Cuanto más alejados están, más dañina es su relación de connascence.

Tipos de connascence

Hay distintos tipos de connascence y se agrupan en dos grupos: estáticos y dinámicos. Los estáticos se pueden observar mirando el código y los dinámicos en tiempo de ejecución. Los tipos de connascence de menos dañinos a más son:

Connascences estáticos

Connascence de nombre

Se da cuando dos entidades están de acuerdo sobre un mismo nombre, y el cambio de este nombre require el cambio en ambas entidades. Un ejemplo de connascence de nombre sería la relación entre una clase a la que definimos con un nombre y todas las variables instancidas que usan esta clase y por lo tanto este nombre. Este connascence es inevitable cuando escribimos código.

Connascence de tipo

Se da cuando dos entidades están de acuerdo sobre un tipo. Esto se da por ejemplo cuando declaramos un método que recibe un parámetro de una clase concreta. Si cambia la clase del parámetro deberán cambiar todos los lugares donde se llama a este método pasandoles un objeto del nuevo tipo para que siga funcionando de manera correcta.

Connascence de significado

Es cuando varios componentes están de acuerdo sobre el significado de un valor determinado. Por ejemplo en el siguiente método el parámetro recivedRequest tiene un tratamiento diferente en función de si el atributo AuthType contiene la cadena «EAP». Hay un acuerdo acerca del significado de la cadena «EAP» en este contexto.

public RadiusPacket processRequest(final RadiusPacket request) {
  String authType = request.getAuthType();
  if (authType != null && authType.equals("EAP")) {
    return eapHandler.executeEAPProcess(request);
  } else {
    return otcHandler.executeVerification(request);
  }
}

Este connascence podría disminuir su nivel a connascence de tipo si se utilizara polimorfismo para crear dos subclases de radiusPacket como en el siguiente fragmento de código:

public RadiusPacket processRequest(final RadiusPacket request) {
  if (request instandeof EAP) {
    return eapHandler.executeEAPProcess(request);
  } else {
    return otcHandler.executeVerification(request);
  }
}

Connascence de posición

Cuando dos componentes tienen que estar de acuerdo en un orden determinado. Por ejemplo en el siguiente código tenemos un método con 3 parámetros. Si el orden de estos parámetros cambiase habría que cambiar de manera necesaria el orden de los parámetros en las llamadas de este método.

process("11231", 1, 3);

Para disminuir el nivel de connascence se podría cambiar los tres parámetros por un parámetro de una clase que los incluyera como atributos, con lo que pasaríamos a tener connascence de tipo.

process(new ProcessRequest("11231", 1, 3));

Connascence de algoritmo

Es cuando varios componentes están de acuerdo acerca de usar un algoritmo. Hace poco me encontré con un ejemplo real de este tipo de connascence. Resulta que se probó por primera vez un servicio que de manera centralizada gestionaba el reporting de otros servicios que utilizaban para ello colas SQS de Amazon, a las que otros servicios enviaban eventos serializados utilizando Jackson para esta serialización. Algunos servicios estaban utilizando la librería Gson para serializar los eventos provocando fallos en la deserialización. Era un caso de connascence de algoritmo ya que todos estos componentes dependían de un algoritmo de serialización específico y si no lo utilizaban fallaban. Este conascence se podría haber evitado creando una librería compartida por todos los servicios, que incluyese un método común para la serialización que encapsulase el algoritmo utilizado.

Connascence dinámicos

Connascence de ejecución

Es cuando el orden de ejecución de dos fragmentos de código es importante. En el siguiente fragmento de código podemos ver el uso de la librería activiti que permite ejecutor flujos Bmp. Para ejecutar un flujo Bpm primero se despliega el flujo y después inicia. Si no se sigue este order este fragmento no funcionaría por lo que hay un connascence de ejecución, ya que hay un acuerdo acerca del orden. Una manera de evitar esto podría llevarse a cabo modificando la librería para que un flujo fuera desplegado si no lo ha sido antes al hacer la llamada para iniciarlo.

repositoryService.createDeployment()
        .addInputStream(deployProcessRequest.getActivitiProcessDefinition().getFile(),
                remoteResourceLoader.loadResource(deployProcessRequest
.getActivitiProcessDefinition()
                        .getFile()))
        .addInputStream(deployProcessRequest.getActivitiProcessDefinition().getImage(),
                remoteResourceLoader.loadResource(deployProcessRequest
.getActivitiProcessDefinition()
                        .getImage()))
        .enableDuplicateFiltering()
        .name(name).deploy();

ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(processKey,
createProcessVariables(initProcessRequest));

Connascence de tiempo

Es cuando el tiempo de ejecución de varios componentes es importante. Esto se da por ejemplo cuando una llamada a un método que consulta algún estado en la base de datos, depende de que la actualización previa de este estado haya terminado y si no lo ha hecho falla. Este connascence se da de forma típica en sofware que maneja problemas de concurrencia.

Connascence de valor

Ocurre cuando varios valores tienen que cambiar juntos. Esto ocurre por ejemplo cuando tenemos una cadena de caracteres repetida a lo largo de nuestro código y si el valor de una de ellas cambia debemos cambiar el valor del resto de apariciones para que siga funcionando. Este ejemplo concreto se podría mejorar creando una constante a la que se hace referencia desde todos las repeticiones, unificando el lugar donde está la cadena y haciendo más sencillo un cambio.

Connascence de identidad

Ocurre cuando varios componentes tienen que hacer referencia al mismo objeto. Esto se daría por ejemplo si varios componentes que usan la misma base de datos necesitasen hacer referencia a una entidad usando su identificador que sería contante. Si esta entidad no existiese o cambiase de identificador el sistema dejaría de funcionar.

Turorial: Pruebas de Integración de Spring Boot con plugin de Docker para Maven

En este tutorial voy a explicar como crear pruebas de integración automáticas en un proyecto de Spring Boot que tiene como dependencia una base de datos Mysql, utilizando para ello la herramienta Docker.

Utilizando Docker podremos ejecutar pruebas de integración utilizando una base de datos real contenida en un contenedor virtual, ahorrándonos tener que instalar una base de datos en nuestro equipo, haciendo la preparación del entorno desarrollo mucho más sencilla y rápida. También obtendremos unas pruebas de integración más cercanas a nuestro entorno de producción que si utilizamos una base de datos embebida tipo h2 porque utilizaremos la misma versión de base de datos que en producción con la única diferencia que estará contenida en un contenedor virtual de Docker.

Docker

downloadDocker es la tecnología que está en boca de todo el mundo últimamente. Pero… ¿Qué es Docker? Es una tecnología que te permite empaquetar aplicaciones con todas sus dependencias en una unidad llamada imagen, que puede ser desplegada dentro de un contenedor virtual basado en tecnología del kernel de Linux. Cada vez que una imagen es desplegada de nuevo en un contenedor virtual comienza en su estado original lo que convierte a Docker en una herramienta de gran utilidad para todo tipo de pruebas de integración. Docker tiene todas las ventajas que podemos tener con una máquina virtual con la diferencia de que es mucho más rápida (el primer despliegue requiere construir o descargar la nueva imagen pero después cada despliegue será mucho más rápido) y su gestión es mucho más sencilla. Docker funciona bajo una licencia de software libre y tiene una comunidad muy activa. Personalmente creo que Docker tiene mucho potencial y va tener múltiples aplicaciones en el futuro, algunas que todavía ni nos imaginamos.

Uso

Desplegar un contenedor virtual con una imagen que contiene Mysql, con una base de datos llamada «db» y con clave de root con valor «root» es tan sencillo como ejecutar el siguiente comando:

docker run -p 3307:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=db -d mysql:latest
Con «docker run» se despliega un contenedor con la imagen que se le pasa como último parámetro en este caso mysql:lastest. Esta es una imagen oficial de Mysql que se encuentra en su página de docker hub. Docker hub es un repositorio público de imágenes de Docker. Con -p se define un mapeo entre puertos del contenedor y de la máquina local, en este caso el puerto por defecto de Mysql 3306 se enlaza con el puerto 3307, dándonos acceso al Mysql del container desde http://localhost:3307. Con -e se definen variables de entorno. Esta imagen está preparada para crear una base de datos con el nombre definido en la variable de entorno MYSQL_DATABASE y con la clave definida en la variable de entorno MYSQL_ROOT_PASSWORD.

Plugin de Docker para Maven

Para este tutorial voy a desplegar la misma imagen del ejemplo anterior de una manera automática antes de ejecutar los test de integración y a destruir el contenedor después. Durante los últimos meses se han creado varios plugins para hacer esto desde Maven. Aquí vamos a utilizar este porque después de probar varios de ellos, es el que he me ha parecido más flexible y fácil de usar.

Ejemplo: Pruebas de Integración usando una base de datos Mysql en un contenedor de Docker

En este caso utilizaré un ejemplo muy sencillo con una entidad usuario y su repositorio usando JPA y explicaré como configurar Maven para lanzar contenedores Docker haciendo uso del plugin citado en la sección anterior. Podéis encontrar el código de este ejemplo completo en este repositorio de github.

Generar proyecto

Primero generar un proyecto Maven con dependencias de JPA y Mysql utilizando  Spring Initializr.

springIinit

Definir entidad User y su repositorio

package com.example.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private long id;
    private String firstName;
    private String lastName;

    protected User() {}

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}
package com.example.repository;

import com.example.domain.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Transactional
public interface UserRepository extends CrudRepository&amp;amp;lt;User, Long&amp;amp;gt; {
List&amp;amp;lt;User&amp;amp;gt; findByFirstNameAndLastName(String firstName, String lastName);
}

Definir prueba de integración para el repositorio

package com.example.repository;

import com.example.Application;
import com.example.domain.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import static org.junit.Assert.assertFalse;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@Transactional
@IntegrationTest
public class UserRepositoryIT {
    @Autowired
    private UserRepository userRepository;

    @Test
    public void contextLoads() {
        userRepository.save(new User(&amp;amp;amp;amp;quot;Name&amp;amp;amp;amp;quot;, &amp;amp;amp;amp;quot;LastName&amp;amp;amp;amp;quot;));

        assertFalse(userRepository.findByFirstNameAndLastName(&amp;amp;amp;amp;quot;Name&amp;amp;amp;amp;quot;, &amp;amp;amp;amp;quot;LastName&amp;amp;amp;amp;quot;).isEmpty());
    }
}

Configurar fichero de propiedades

spring.datasource.url = jdbc:mysql://localhost:3307/db
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto = update

Configurar docker-maven-plugin para desplegar el contenedor de Docker con una imagen con base de datos Mysql el fichero pom

Para configurar este plugin tenemos que definir dos bloques después de instanciarlo: configuration y executions.

El en el primer bloque se define que imagen se lanza y con que configuración. En este ejemplo obtendremos un efecto similar al que tendríamos ejecutando el comando antes expuesto, ya que he utilizado los mismos parámetros:

docker run -p 3307:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=db -d mysql:latest

La única diferencia es la sección wait donde se definen reglas para aplicar una espera antes de lanzar las pruebas de integración. En este caso la regla define que se espere a que Mysql este disponible en el puerto 3306 del contenedor.

En  el segundo bloque definimos que el contenedor se cree antes de las pruebas de integración y se destruya después de ellas:

<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<images>
<image>
<name>mysql:latest</name>
<run>
<ports>
<port>3307:3306</port>
</ports>
<env>
<MYSQL_ROOT_PASSWORD>root</MYSQL_ROOT_PASSWORD>
<MYSQL_DATABASE>db</MYSQL_DATABASE>
</env>
<wait>
<time>20000</time>
<tcp>
<host>localhost</host>
<ports>
<port>3306</port>
</ports>
</tcp>
</wait>
</run>
</image>
</images>
</configuration>
<executions>
<execution>
<id>start</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>

Finalmente debemos configurar el plugin maven-failsafe-plugin:

<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.17</version>
<executions>
<execution>
<id>integration-test</id>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>

Después de estos pasos podemos verificar que las pruebas de integración se ejecutan correctamente ejecutando mvn verify.

screenshootç