Al trabajar con Flyway es muy común encontrarse con multitud de scripts versionados. Aprende en este tutorial técnicas para refactorizar con Flyway.
0. Índice de contenidos
- Introducción
- Entorno
- Configuración
- Uso de Flyway
- Organización de la migración
- Refactorizar con Flyway
- Conclusiones
- Enlaces y referencias
1. Introducción
Flyway es una herramienta de migración de base de datos. Favorece la simplicidad y la convención sobre la configuración para ahorrarnos tiempo en nuestro día a día. Si nunca has oído hablar de esta biblioteca échale un ojo al tutorial de Flyway sobre cómo se integra con Maven de nuestro compañero Yair.
Actualmente, es bastante común empezar un proyecto sobre el cual pueden cambiarte los requisitos o simplemente, se ven los conceptos más claros y es necesario refactorizar.
En este tutorial os hablaré principalmente de la posibilidad de editar el esquema propio de Flyway y cómo agrupar todos los scripts versionados hasta la fecha de una forma razonable.
2. Entorno
Para escribir el tutorial se utilizó el siguiente entorno:
- Hardware: MacBook Pro (2,5 GHz i7 4 núcleos, 16 GB 1600 MHz DDR3)
- Sistema Operativo: Mac OS X Catalina 10.15.3
- Entorno de desarrollo: InteiIJ Ultimate 2019.3
- Java 8 (jdk version 1.8.0_231)
- Maven (3.6.2)
- Docker Desktop (2.2.0.3) y docker-engine (19.03)
- Docker-compose (1.25.4)
- Flyway 6.3.1
- Git (2.23)
3. Configuración
Vamos a utilizar una aplicación de Spring Boot para conectarnos a una base de datos PostgreSQL.
Para crear el proyecto desde cero, he utilizado la web de inicio de SpringIO. Donde he elegido:
- Maven
- Java
- Spring Boot 2.2.5 (la última estable)
- Jar
- Java 8
- Dependencias:
- PostgreSQL Driver
- Spring Web
- Spring Data JPA
- Flyway Migration
Le damos al botón de generar y guardamos el proyecto.
4. Uso de Flyway
4.1. Arrancamos el proyecto
Para no demorarnos, vamos a seguir el tutorial partiendo de un proyecto base ya desarrollado para simular un proyecto en marcha con sus propios tests de integración. Este proyecto gestiona productos y otros items mediante peticiones POST y GET. Para descargarnos el proyecto de GitHub, realizamos lo siguiente:
git clone https://github.com/asegnz/product-flyway.git
Compilamos el proyecto con Maven introduciendo el siguiente comando:
mvn clean install
Como vamos a levantar la aplicación de forma manual, levantaremos antes sus dependencias con la base de datos (en este caso PostgreSQL). Introduciendo el siguiente comando desde la carpeta raíz del proyecto:
docker-compose up
Mientras arrancamos la base de datos, abrimos otra terminal y levantamos la aplicación siguiendo los siguientes comandos desde la raíz del proyecto:
cd target java -jar product-application.jar
Cuando levantamos por primera vez la aplicación, por defecto se realiza la validación de los scripts de migración y posteriormente se procede a la migración de forma automática. Veamos un ejemplo del log que deja la aplicación al migrar con éxito:
Flyway Community Edition 6.0.8 by Redgate HikariPool-1 - Starting... HikariPool-1 - Start completed. Database: jdbc:postgresql://localhost:5433/postgres (PostgreSQL 10.10) Successfully validated 3 migrations (execution time 00:00.021s) Creating Schema History table "public"."flyway_schema_history" ... Current version of schema "public": << Empty Schema >> Migrating schema "public" to version 0001 - create table product Migrating schema "public" to version 0002 - create table item Migrating schema "public" to version 0003 - create table client Successfully applied 3 migrations to schema "public" (execution time 00:00.128s)
Como podemos observar en el log, hemos versionado tres scripts donde se crean tres tablas. Estos scripts están definidos en src/main/resources/db.migration que es la carpeta por defecto donde Flyway busca scripts para migrar.
4.2. Migración con Flyway
Esta migración automática se produce gracias a la dependencia org.flywaydb:flyway-core con la cual se integra Spring Boot. Spring Boot enlaza Flyway con su DataSource y lo invoca al arrancar. De esta manera podemos utilizar las propiedades de Flyway directamente desde nuestra aplicación de Spring Boot y configurarlas directamente en el application.properties de la carpeta src/main/resources. Podemos configurar la ubicación de la carpeta de migraciones a dónde más nos interese con la siguiente configuración:
spring.flyway.locations=classpath:db/migration
Tras migrar los tres scripts a la base de datos, Flyway ya ha creado la tabla flyway_schema_history con meta-información de los scripts ya migrados y guarda su checksum para que no pueda ser modificado. Adjunto imagen del contenido de la tabla:
Cada vez que levantamos la aplicación Flyway hará una de estas dos cosas:
- Si no existe la tabla, la creará y migrará todo lo que encuentre en la configuración spring.flyway.locations.
- En caso de existir la tabla, comprobará la versión de scripts aplicados con respecto a los que migramos desde la aplicación y validará que el checksum coincide. Si la base de datos está desactualizada, procederá a migrar lo que le falte.
Flyway ofrece muchas opciones para configurar el comportamiento de arranque, el que indico aquí es el de por defecto.
5. Organización de la migración
Hemos montado un proyecto sobre el cual podemos organizar la definición y el modelo de la base de datos. Al añadir un nuevo script versionado cada vez que se inicie la aplicación, la base de datos se actualizará con el nuevo script. Recordemos que es obligatorio añadir la versión al script.
A medida que pasa el tiempo en el proyecto, un gran cúmulo de scripts en una misma carpeta no parece que sea la solución más limpia y podría existir otra manera mejor de agrupación. Sin embargo, hay dos puntos de interés que debemos tener en cuenta en el momento de realizar esta tarea:
- Existen tests de integración que levantan una base de datos nueva cada vez que se ejecutan
- También tenemos diferentes entornos de desarrollo, integración, qa, pre-producción y producción con distintos versionados de base de datos. Antes de tocar nada, hay que asegurarse de que tenemos todas las bases de datos alineadas.
Además, existe una limitación en Flyway: no podemos editar un script de migración si ya se ha migrado a un entorno. Sin embargo, esto no es una limitación para poder mover este script a una carpeta y así poder ordenar nuestros scripts de la manera más conveniente.
6. Refactorizar con Flyway
Existen varias maneras de organizar los scripts de forma eficiente de las que hablaremos a continuación.
6.1 Organización por carpetas
De forma automática y sin tener que configurar nada podemos crear un esquema de organización por carpetas que debajo de lo considerado como locations en Flyway. A continuación, dejo un simple ejemplo de versionado por release:
- Carpeta src/main/resources/db/migration
- Carpeta R0
- Script V0001__create_table_product.sql
- Script V0002__create_table_item.sql
- Script V0003__create_table_client.sql
- Carpeta R1
- Script V1000__create_table_discount.sql
- Carpeta R0
Esta idea nos permite organizar los scripts de migración de forma cómoda. Para este caso, hemos elegido una organización por release. Podríamos en un futuro cercano refactorizar las carpetas y cambiar los scripts de las mismas, siempre y cuando mantengamos invariable el nombre y el tamaño de cada script.
En contra de esta idea tenemos lo siguiente: va a seguir creciendo el número de scripts y carpetas en nuestro proyecto. Esto hará más lenta la carga de las migraciones en nuevas bases de datos como sucede en los tests de integración.
6.2 Agrupamos los scripts y declaramos un baseline
En determinadas épocas del proyecto es común que nos pongamos a refactorizar para mejorar la legibilidad del código. Este proceso de refactorización también se puede hacer con Flyway, ya que puede ser más eficiente el agrupar todos los scripts migrados hasta la fecha en único fichero. En esta opción vamos a:
- Agrupar todos los scripts en uno solo
- Limpiar el esquema de Flyway
- Declarar una versión como baseline para que se migre a partir de ésta
6.2.1 Preparamos la prueba
Para agrupar los scripts en uno solo se pueden utilizar herramientas de exportación de tablas de la base de datos. En nuestro caso y por simplificar mucho este proceso vamos a concatenar los tres scripts por consola.
Para nuestro ejemplo, disponemos de tres ficheros en la carpeta src/main/resources/db/migration:
- V0001__create_table_product.sql
- V0002__create_table_item.sql
- V0003__create_table_client.sql
En dicha carpeta, para concatenar los tres ficheros en uno solo, vamos a ejecutar el siguiente comando:
cat *.sql > V1000__all_created_tables_db.sql
A partir de aquí, ya podríamos borrar los scripts originales, recreamos la tabla flyway_schema_history y vamos a declarar la versión baseline V1000. Flyway ignorará todos los scripts por debajo o igual de esta versión y migrará sólo las versiones superiores. Esta acción la tendremos que replicar para todos los entornos.
6.2.2 Declarando el Baseline
Para realizar estos pasos, por simplificar he utilizado una clase Java con un método principal main donde configuramos la base de datos y marcamos las directrices a ejecutar:
public class FlywayUtil { public static void main(String[] args) { final String url = "jdbc:postgresql://localhost:5432/postgres"; final String user = "postgres"; final String password = "password"; Map<String, String> map = new HashMap<>(); map.put("flyway.baselineVersion", "1000"); Flyway flyway = Flyway.configure().dataSource(url, user, password).configuration(map).load(); flyway.clean(); flyway.baseline(); } }
Tras ejecutar esta clase, se ha eliminado y creado la tabla flyway_schema_history gracias a flyway.clean() e insertado un registro indicando que es Baseline y la versión base (V1000).
Actualmente, si migramos de nuevo arrancando la aplicación con los siguientes scripts:
- V0001__create_table_product.sql
- V0002__create_table_item.sql
- V0003__create_table_client.sql
- V1000__all_created_tables_db.sql
- V1001__create_table_discount.sql
Únicamente nos va a migrar el último script por ser el único que supera la versión baseline (V1000) que hemos declarado.
Si compilamos el proyecto de nuevo y pasamos a la fase de tests de integración levantaremos una nueva base de datos sin un baseline declarado, por tanto, se van a ejecutar todos los scripts. Para esta acción necesitaremos borrar los scripts por debajo de la versión V1000 porque darán conflictos.
7. Conclusiones
Flyway es una potente herramienta para gestionar y automatizar la migración sobre la base de datos. Sin embargo, a veces se hace molesto ver multitud de scripts en proyectos que ya tienen recorrido y se hace necesario un borrón y cuenta nueva.
En este tutorial hemos aprendido a:
- Integrar de forma breve Flyway en una aplicación de Spring Boot
- Organizar de mejor forma los scripts
- Declarar un baseline de Flyway para futuras migraciones
8. Enlaces y Referencias
- Código fuente para jugar de este tutorial
- Documentación de Flyway
- Documentación relativa al baseline