Docker-compose con Gradle

Probablemente este post a los puristas del TDD les resulte una aberración porque lo que deberías es fomentar el uso de los test para guiar el desarrollo de tu aplicación.

En esta idea una de las batallas que tienes que lidiar suele ser el tema de cómo preparar una infraestructura para estos test. Por ejemplo, tu aplicación requiere una base de datos y te toca usar un H2, pero puede ser más complicado y requerir además un Redis, un RabbitMQ etc por lo que terminas usando test-containers (todo un acierto)

Todo eso está muy bien pero a veces el desarrollo no puede, o no quieres, que sea guiado por los test. A veces tienes que montar todo el entorno y tirar de depurador para reproducir un comportamiento extraño, por ejemplo. Y casi siempre esto lo resuelves montando un docker-compose donde configuras toda la infra y/o servicios requeridos por tu aplicación, donde tienes que lidiar con puertos, persistencia , levantar los servicios etc

En este post vamos a ver cómo podemos "integrar" este docker-compose con nuestro build.gradle de tal forma que podamos levantar esos servicios e integrarlos en nuestra app de forma cómoda integrandola en nuestras task. Para ello vamos a usar el plugin com.avast.gradle.docker-compose

Docker-compose

Supongamos que nuestra arquitectura requiere de una base de datos MySQL, un Redis y un servicio propio tuyo (distinto del que estás trabajando). Probablemente nuestro docker-compose de desarrollo sería algo como:

version: "3.5"
services:

  mysql:
    image: mysql:5.6
    ports:
      - 3306
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"

  cache:
    image: redis:6.2-alpine
    restart: always
    ports:
      - 6379
    command: redis-server --save 20 1 --loglevel warning --requirepass tupwd

  business:
    image: pvidasoftware/greatapp
    ports:
      - 8080

Probablmente tu docker-compose tenga una configuracion distinta para los puertos y mapees un puerto del contenedor con un puerto fijo de tu maquina para poder así acceder al servicio. No es mala idea pero al final es un infierno de configuración cuando el proyecto es compartido entre varios miembros y hay que ponerse de acuerdo en qué puertos usamos para qué cosas

Gradle

En primer lugar vamos a incluir el plugin de avast en nuestro build:

build.gradle
plugins {
    id "java" (1)
    ....
    id "com.avast.gradle.docker-compose" version "0.15.2" (2)
}
1 Si es un proyecto java sueles tener este plugin, pero no es necesario para este post
2 Añadimos el pluign de Avast, la ultima version a día de hoy

Esto nos va añadir una serie de tasks en el grupo docker como por ejemplo composeUp y composeDown que obviamente sirven para gestionar un docker-compose que le indiquemos

En segundo lugar vamos a indicarle al plugin donde reside nuestro docker-compose

build.gradle
dockerCompose {
    useComposeFiles.add("infra/docker-compose.yml")
    isRequiredBy(tasks.run)
}

En este ejemplo le estamos diciendo que tenemos un docker-compose en el directorio infra y que la tarea que está interesada en controlar el estado del docker-compose es run (tipica tarea para correr el java)

Por último vamos a "enlazar" los servicios del docker con nuestra tarea que ejecuta la aplicación mediante variables de entorno:

build.gradle
run.doFirst {
    dockerCompose.exposeAsEnvironment(run)

    def mysqlInfo = dockerCompose.servicesInfos.mysql.firstContainer (1)
    def redisInfo = dockerCompose.servicesInfos.cache.firstContainer (2)
    def businessInfo = dockerCompose.servicesInfos.business.firstContainer (3)

    environment "JDBC_URL", "mysql://${mysqlInfo.host}:${mysqlInfo.ports[3306]}/"

    environment "REDIS_HOST", redisInfo.host
    environment "REDIS_PORT", redisInfo.ports[6379]

    environment "SERVICE_API_URL", "http://${businessInfo.host}:${businessInfo.ports[8080]}/api/"

}
1 servicesInfos.mysql obtiene info del servicio llamado mysql en el docker-compose
2 servicesInfos.cache obtiene info del servicio llamado cache en el docker-compose
3 servicesInfos.business obtiene info del servicio llamado business en el docker-compose

El plugin se encarga de levantar los servicios y proporcionarnos información sobre los puertos donde está escuchando cada uno, información que usamos para configurar nuestra tarea. En este ejemplo configuramos un jdbc, un host y un puerto y una url a un api que son las variables que se supone requiere nuestra aplicacion para poder ejecutarse correctamente.

Cuando ejecutemos la tarea run, los servicios se levantan, se ejecuta nuestra aplicación y cuando paramos la tarea, el plugin se encarga de parar el docker-compose (alguna vez me ha fallado y me ha tocado pararlos a mano usando la tarea composeDown)

NOTE

Este plugin tiene unas cuantas configuraciones interesantes adicionales que iré investigando

Follow comments at Telegram group Or subscribe to the Channel Telegram channel

2019 - 2022 | Mixed with Bootstrap | Baked with JBake v2.6.7