Saltar al contenido principal

Gestión de Migraciones Versionadas e Integridad del Directorio de Migraciones

· 9 min de lectura
[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Hace cinco semanas lanzamos una característica muy esperada para gestionar cambios en bases de datos con Ent: Migraciones Versionadas. En el artículo de anuncio hicimos una breve introducción a los enfoques declarativo y basado en cambios para mantener los esquemas de bases de datos sincronizados con las aplicaciones que los consumen, así como sus inconvenientes y por qué el enfoque de Atlas (el motor de migraciones subyacente de Ent) para combinar lo mejor de ambos mundos en un flujo de trabajo merece la pena. Lo llamamos Creación de Migraciones Versionadas y si aún no lo has leído, ¡es buen momento para hacerlo!

Con la creación de migraciones versionadas, los archivos de migración resultantes siguen siendo "basados en cambios", pero han sido planificados de forma segura por el motor de Atlas. Esto significa que puedes seguir usando tu herramienta de gestión de migraciones favorita, como Flyway, Liquibase, golang-migrate/migrate o pressly/goose cuando desarrolles servicios con Ent.

En este artículo quiero mostrarte otra nueva característica del proyecto Atlas que llamamos Archivo de Integridad del Directorio de Migraciones, que ahora es compatible con Ent, y cómo puedes usarla con cualquiera de las herramientas de gestión de migraciones que ya conoces y te gustan.

El problema

Al usar migraciones versionadas, los desarrolladores deben evitar lo siguiente para no dañar la base de datos:

  1. Modificar retroactivamente migraciones ya ejecutadas.

  2. Cambiar accidentalmente el orden de las migraciones.

  3. Confirmar scripts SQL semánticamente incorrectos. Teóricamente, la revisión de código debería proteger a los equipos de fusionar migraciones con estos problemas. Sin embargo, en mi experiencia, muchos errores pueden pasar desapercibidos para el ojo humano, haciendo este enfoque propenso a fallos. Por ello, una forma automatizada de prevenir estos errores es mucho más segura.

El primer problema (modificar el historial) lo abordan la mayoría de herramientas guardando un hash del archivo de migración aplicado en la base de datos gestionada y comparándolo con los archivos locales. Si no coinciden, se puede abortar la migración. Sin embargo, esto ocurre muy tarde en el ciclo de desarrollo (durante el despliegue), y detectarlo antes ahorraría tiempo y recursos.

Para el segundo (y tercer) problema, considera este escenario:

Diagrama de migraciones versionadas sin conflicto

Este diagrama muestra dos errores que pasan desapercibidos. El primero es el orden de los archivos de migración.

El Equipo A y el Equipo B crean ramas para nuevas funcionalidades aproximadamente al mismo tiempo. El Equipo B genera un archivo de migración con marca de tiempo de versión x y sigue trabajando. El Equipo A genera un archivo de migración más tarde, con marca de tiempo x+1. El Equipo A completa su funcionalidad y la fusiona en master, desplegándola posiblemente en producción con la migración x+1 aplicada. Hasta aquí, todo correcto.

Ahora el Equipo B fusiona su funcionalidad con la migración versión x, que es anterior a la versión x+1 ya aplicada. Si la revisión de código no detecta esto, el archivo de migración llega a producción, y dependerá de la herramienta de gestión de migraciones específica decidir qué ocurre.

La mayoría de herramientas tienen su propia solución para este problema. Por ejemplo, pressly/goose adopta un enfoque que denominan versionado híbrido. Antes de presentarles la solución única de Atlas (Ent) para este problema, echemos un vistazo rápido al tercer punto:

Si tanto el Equipo A como el Equipo B desarrollan una funcionalidad que requiere nuevas tablas o columnas, y les dan el mismo nombre (por ejemplo users), ambos podrían generar una sentencia para crear esa tabla. Mientras que el equipo que fusiona primero tendrá una migración exitosa, la migración del segundo equipo fallará porque la tabla o columna ya existe.

La solución

Atlas tiene un enfoque único para abordar los problemas anteriores. El objetivo es detectar estos problemas lo antes posible. En nuestra opinión, el mejor lugar para hacerlo es en el control de versiones y en las fases de integración continua (CI) de un producto. La solución de Atlas es la introducción de un nuevo archivo que llamamos Archivo de Integridad del Directorio de Migraciones. Se trata simplemente de otro archivo llamado atlas.sum que se almacena junto con los archivos de migración y contiene metadatos sobre el directorio de migraciones. Su formato está inspirado en el archivo go.sum de un módulo de Go, y tendría un aspecto similar a este:

h1:KRFsSi68ZOarsQAJZ1mfSiMSkIOZlMq4RzyF//Pwf8A=
20220318104614_team_A.sql h1:EGknG5Y6GQYrc4W8e/r3S61Aqx2p+NmQyVz/2m8ZNwA=

El archivo atlas.sum contiene un resumen (sum) de todo el directorio como primera entrada, y un checksum para cada uno de los archivos de migración (implementado mediante un árbol de hash Merkle inverso de una rama). Veamos cómo podemos usar este archivo para detectar los casos anteriores en el control de versiones y CI. Nuestro objetivo es alertar sobre el hecho de que ambos equipos han añadido migraciones y que probablemente deban verificarse antes de proceder a la fusión.

:::nota Para seguir el ejemplo, ejecuta los siguientes comandos y crea rápidamente un caso práctico:

  1. Crea un módulo de Go y descarga todas las dependencias necesarias
  2. Crea un esquema básico de Usuario
  3. Habilita la función de migraciones versionadas
  4. Ejecuta la generación de código
  5. Inicia un contenedor Docker de MySQL (elimínalo con docker stop atlas-sum)
mkdir ent-sum-file
cd ent-sum-file
go mod init ent-sum-file
go install entgo.io/ent/cmd/ent@master
go run entgo.io/ent/cmd/ent new User
sed -i -E 's|^//go(.*)$|//go\1 --feature sql/versioned-migration|' ent/generate.go
go generate ./...
docker run --rm --name atlas-sum --detach --env MYSQL_ROOT_PASSWORD=pass --env MYSQL_DATABASE=ent -p 3306:3306 mysql

:::

El primer paso es indicar al motor de migraciones que cree y gestione el archivo atlas.sum usando la opción schema.WithSumFile(). El siguiente ejemplo utiliza un cliente Ent instanciado para generar nuevos archivos de migración:

package main

import (
"context"
"log"
"os"

"ent-sum-file/ent"

"ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect/sql/schema"
_ "github.com/go-sql-driver/mysql"
)

func main() {
client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/ent")
if err != nil {
log.Fatalf("failed connecting to mysql: %v", err)
}
defer client.Close()
ctx := context.Background()
// Create a local migration directory.
dir, err := migrate.NewLocalDir("migrations")
if err != nil {
log.Fatalf("failed creating atlas migration directory: %v", err)
}
// Write migration diff.
err = client.Schema.NamedDiff(ctx, os.Args[1], schema.WithDir(dir), schema.WithSumFile())
if err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
}

Tras crear un directorio de migraciones y ejecutar los comandos anteriores, deberías ver archivos de migración compatibles con golang-migrate/migrate y, además, el archivo atlas.sum con el siguiente contenido:

mkdir migrations
go run -mod=mod main.go initial
20220504114411_initial.up.sql
-- create "users" table
CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`)) CHARSET utf8mb4 COLLATE utf8mb4_bin;

20220504114411_initial.down.sql
-- reverse: create "users" table
DROP TABLE `users`;

atlas.sum
h1:SxbWjP6gufiBpBjOVtFXgXy7q3pq1X11XYUxvT4ErxM=
20220504114411_initial.down.sql h1:OllnelRaqecTrPbd2YpDbBEymCpY/l6ihbyd/tVDgeY=
20220504114411_initial.up.sql h1:o/6yOczGSNYQLlvALEU9lK2/L6/ws65FrHJkEk/tjBk=

Como puedes observar, el archivo atlas.sum contiene una entrada por cada archivo de migración generado. Con la generación del archivo atlas.sum habilitada, tanto el Equipo A como el Equipo B tendrán este archivo cuando generen migraciones para un cambio en el esquema. Ahora el control de versiones mostrará un conflicto de fusión cuando el segundo equipo intente fusionar su funcionalidad.

Migraciones versionadas de Atlas sin conflicto

:::nota En los siguientes pasos invocamos la CLI de Atlas mediante go run -mod=mod ariga.io/atlas/cmd/atlas, pero también puedes instalar la CLI globalmente en tu sistema (y luego invocarla simplemente con atlas) siguiendo las instrucciones de instalación aquí. :::

Puedes verificar en cualquier momento si tu archivo atlas.sum está sincronizado con el directorio de migraciones usando el siguiente comando (que no debería mostrar errores en este momento):

go run -mod=mod ariga.io/atlas/cmd/atlas migrate validate

Sin embargo, si realizas cambios manuales en tus archivos de migración, como añadir una nueva sentencia SQL, editar una existente o incluso crear un archivo completamente nuevo, el archivo atlas.sum dejará de estar sincronizado con el contenido del directorio de migraciones. Al intentar generar nuevos archivos de migración para un cambio de esquema, el motor de migraciones de Atlas bloqueará la operación. Pruébalo creando un nuevo archivo de migración vacío y ejecutando nuevamente el main.go:

go run -mod=mod ariga.io/atlas/cmd/atlas migrate new migrations/manual_version.sql --format golang-migrate
go run -mod=mod main.go initial
# 2022/05/04 15:08:09 failed creating schema resources: validating migration directory: checksum mismatch
# exit status 1

El comando atlas migrate validate mostrará el mismo problema:

go run -mod=mod ariga.io/atlas/cmd/atlas migrate validate
# Error: checksum mismatch
#
# You have a checksum error in your migration directory.
# This happens if you manually create or edit a migration file.
# Please check your migration files and run
#
# 'atlas migrate hash --force'
#
# to re-hash the contents and resolve the error.
#
# exit status 1

Para volver a sincronizar el archivo atlas.sum con el directorio de migraciones, podemos usar nuevamente la CLI de Atlas:

go run -mod=mod ariga.io/atlas/cmd/atlas migrate hash --force

Como medida de seguridad, la CLI de Atlas no opera sobre un directorio de migraciones que no esté sincronizado con su archivo atlas.sum. Por lo tanto, necesitarás añadir el flag --force al comando.

Para casos donde un desarrollador olvide actualizar el archivo atlas.sum después de hacer cambios manuales, puedes añadir una llamada atlas migrate validate a tu CI. Estamos trabajando activamente en una acción de GitHub y solución de CI que hace esto (entre otras cosas) por ti listo para usar.

Conclusión

En este post, hemos introducido brevemente fuentes comunes de problemas en migraciones de esquemas cuando se trabaja con archivos SQL basados en cambios, y presentamos una solución basada en el proyecto Atlas para hacer las migraciones más seguras.

¿Tienes preguntas? ¿Necesitas ayuda para comenzar? Únete a nuestro Servidor de Discord de Ent.

[Para más noticias y actualizaciones de Ent:]