1. Resumen del Mecanismo de Migración

1.1 Concepto y Rol de la Migración

La migración de base de datos es el proceso de sincronizar cambios en modelos de datos con la estructura de la base de datos, lo cual es crucial para la persistencia de datos. A medida que la versión de la aplicación itera, el modelo de datos a menudo experimenta cambios, como agregar o eliminar campos o modificar índices. La migración permite a los desarrolladores gestionar estos cambios de manera versionada y sistemática, asegurando la consistencia entre la estructura de la base de datos y el modelo de datos.

En el desarrollo web moderno, el mecanismo de migración proporciona los siguientes beneficios:

  1. Control de versión: Los archivos de migración pueden rastrear el historial de cambios de la estructura de la base de datos, facilitando la reversión y comprensión de los cambios en cada versión.
  2. Implementación automatizada: A través del mecanismo de migración, la implementación y actualizaciones de la base de datos pueden automatizarse, reduciendo la posibilidad de intervención manual y el riesgo de errores.
  3. Colaboración en equipo: Los archivos de migración garantizan que los miembros del equipo utilicen estructuras de base de datos sincronizadas en diferentes entornos de desarrollo, facilitando el desarrollo colaborativo.

1.2 Características de Migración del Marco ent

La integración del marco ent con el mecanismo de migración ofrece las siguientes características:

  1. Programación Declarativa: Los desarrolladores solo necesitan enfocarse en la representación de entidades en Go, y el marco ent manejará la conversión de entidades a tablas de base de datos.
  2. Migración Automática: ent puede crear y actualizar automáticamente las estructuras de tablas de base de datos sin la necesidad de escribir manualmente declaraciones DDL.
  3. Control Flexible: ent proporciona varias opciones de configuración para satisfacer diferentes requisitos de migración, como con o sin restricciones de clave externa y generación de IDs globalmente únicos.

2. Introducción a la Migración Automática

2.1 Principios Básicos de la Migración Automática

La característica de migración automática del marco ent se basa en los archivos de definición de esquema (generalmente encontrados en el directorio ent/schema) para generar la estructura de la base de datos. Después de que los desarrolladores definen entidades y relaciones, ent inspeccionará la estructura existente en la base de datos y generará operaciones correspondientes para crear tablas, agregar o modificar columnas, crear índices, etc.

Además, el principio de migración automática de ent funciona en un "modo de añadido": por defecto solo añade nuevas tablas, nuevos índices o agrega columnas a tablas, y no elimina tablas o columnas existentes. Este diseño es beneficioso para evitar la pérdida accidental de datos y facilita la expansión de la estructura de la base de datos de manera progresiva.

2.2 Uso de la Migración Automática

Los pasos básicos para utilizar la migración automática de ent son los siguientes:

paquete principal

import (
    "contexto"
    "registro"
    "ent"
)

función principal() {
    cliente, err := ent.Abrir("mysql", "root:pass@tcp(localhost:3306)/test")
    si err != nil {
        registro.Fatalf("Error al conectar a MySQL: %v", err)
    }
    defer cliente.Cerrar()
    ctx := contexto.Background()

    // Realizar migración automática para crear o actualizar el esquema de la base de datos
    si err := cliente.Schema.Crear(ctx); err != nil {
        registro.Fatalf("Error al crear el esquema de la base de datos: %v", err)
    }
}

En el código anterior, ent.Abrir es el encargado de establecer una conexión con la base de datos y devolver una instancia de cliente, mientras que cliente.Schema.Crear ejecuta la operación real de migración automática.

3. Aplicaciones Avanzadas de la Migración Automática

3.1 Eliminar Columnas e Índices

En algunos casos, es posible que necesitemos eliminar columnas o índices que ya no son necesarios de la estructura de la base de datos. En ese momento, podemos utilizar las opciones WithDropColumn y WithDropIndex. Por ejemplo:

// Ejecutar migración con opciones para eliminar columnas e índices.
err = cliente.Schema.Crear(
    ctx,
    migrar.WithDropIndex(true),
    migrar.WithDropColumn(true),
)

Este fragmento de código habilita la configuración para eliminar columnas e índices durante la migración automática. ent eliminará cualquier columna e índice que no exista en la definición de esquema al ejecutar la migración.

3.2 ID Universal Único

Por defecto, las claves primarias en bases de datos SQL comienzan desde 1 para cada tabla, y diferentes tipos de entidad pueden compartir el mismo ID. En algunos escenarios de aplicación, como al utilizar GraphQL, puede ser necesario proporcionar unicidad global para los IDs de objetos de diferentes tipos de entidad. En ent, esto se puede configurar utilizando la opción WithGlobalUniqueID:

// Ejecutar migración con IDs únicos universales para cada entidad.
si err := cliente.Schema.Crear(ctx, migrar.WithGlobalUniqueID(true)); err != nil {
    registro.Fatalf("Error al crear el esquema de la base de datos: %v", err)
}

Después de habilitar la opción WithGlobalUniqueID, ent asignará un rango de ID de 2^32 a cada entidad en una tabla llamada ent_types para lograr una unicidad global.

3.3 Modo sin conexión

El modo sin conexión permite escribir cambios en el esquema a un io.Writer en lugar de ejecutarlos directamente en la base de datos. Es útil para verificar comandos SQL antes de que los cambios surtan efecto o para generar un script SQL para ejecución manual. Por ejemplo:

// Volcar los cambios de la migración a un archivo
f, err := os.Create("migrate.sql")
if err != nil {
    log.Fatalf("Error al crear el archivo de migración: %v", err)
}
defer f.Close()
if err := client.Schema.WriteTo(ctx, f); err != nil {
    log.Fatalf("Error al imprimir los cambios del esquema de la base de datos: %v", err)
}

Este código escribirá los cambios de la migración en un archivo llamado migrate.sql. En la práctica, los desarrolladores pueden optar por imprimir directamente en la salida estándar o escribir en un archivo para revisión o registro.

4. Soporte de Claves Foráneas y Ganchos Personalizados

4.1 Habilitar o Deshabilitar Claves Foráneas

En Ent, las claves foráneas se implementan definiendo relaciones (aristas) entre entidades, y estas relaciones de clave foránea se crean automáticamente a nivel de base de datos para hacer cumplir la integridad y consistencia de los datos. Sin embargo, en ciertas situaciones, como para la optimización del rendimiento o cuando la base de datos no admite claves foráneas, puedes elegir deshabilitarlas.

Para habilitar o deshabilitar las restricciones de las claves foráneas en migraciones, puedes controlarlo a través de la opción de configuración WithForeignKeys:

// Habilitar claves foráneas
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(true), 
)
if err != nil {
    log.Fatalf("Error al crear recursos de esquema con claves foráneas: %v", err)
}

// Deshabilitar claves foráneas
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(false), 
)
if err != nil {
    log.Fatalf("Error al crear recursos de esquema sin claves foráneas: %v", err)
}

Esta opción de configuración debe ser pasada al llamar a Schema.Create, y determina si incluir restricciones de claves foráneas en el DDL generado basado en el valor especificado.

4.2 Aplicación de Ganchos de Migración

Los ganchos de migración son lógicas personalizadas que pueden ser insertadas y ejecutadas en diferentes etapas de la ejecución de la migración. Son muy útiles para realizar lógica específica en la base de datos antes/después de la migración, como validar resultados de la migración y rellenar datos previamente.

Aquí tienes un ejemplo de cómo implementar ganchos de migración personalizados:

func customHook(next schema.Creator) schema.Creator {
    return schema.CreateFunc(func(ctx context.Context, tables ...*schema.Table) error {
        // Código personalizado a ejecutar antes de la migración
        // Por ejemplo, registro, comprobación de ciertas condiciones previas, etc.
        log.Println("Lógica personalizada antes de la migración")
        
        // Llamar al siguiente gancho o a la lógica de migración por defecto
        err := next.Create(ctx, tables...)
        if err != nil {
            return err
        }
        
        // Código personalizado a ejecutar después de la migración
        // Por ejemplo, limpieza, migración de datos, comprobaciones de seguridad, etc.
        log.Println("Lógica personalizada después de la migración")
        return nil
    })
}

// Uso de ganchos personalizados en la migración
err := client.Schema.Create(
    ctx,
    schema.WithHooks(customHook),
)
if err != nil {
    log.Fatalf("Error al aplicar ganchos personalizados de migración: %v", err)
}

Los ganchos son herramientas poderosas e indispensables para migraciones complejas, dándote la capacidad de controlar directamente el comportamiento de la migración de la base de datos cuando sea necesario.

5. Migraciones Versionadas

5.1 Introducción a las Migraciones Versionadas

La migración versionada es un patrón para gestionar la migración de base de datos, permitiendo a los desarrolladores dividir los cambios en la estructura de la base de datos en múltiples versiones, cada una conteniendo un conjunto específico de comandos de modificación de la base de datos. En comparación con la Migración Automática, la migración versionada proporciona un control más detallado, asegurando la trazabilidad y reversibilidad de los cambios en la estructura de la base de datos.

La principal ventaja de la migración versionada es su soporte para migración hacia adelante y hacia atrás (es decir, actualización o reversión), permitiendo a los desarrolladores aplicar, revertir o saltar cambios específicos según sea necesario. Al colaborar en un equipo, la migración versionada asegura que cada miembro trabaje en la misma estructura de base de datos, reduciendo los problemas causados por las inconsistencias.

La migración automática es frecuentemente irreversible, generando y ejecutando declaraciones SQL para que coincidan con el estado más reciente de los modelos de entidades, principalmente usada en etapas de desarrollo o en proyectos pequeños.

5.2 Uso de Migraciones Versionadas

1. Instalación de la herramienta Atlas

Antes de utilizar las migraciones versionadas, es necesario instalar la herramienta Atlas en tu sistema. Atlas es una herramienta de migración que admite varios sistemas de bases de datos, ofreciendo funciones potentes para gestionar los cambios en el esquema de la base de datos.

macOS + Linux

curl -sSf https://atlasgo.sh | sh

Homebrew

brew install ariga/tap/atlas

Docker

docker pull arigaio/atlas
docker run --rm arigaio/atlas --help

Windows

https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe

2. Generación de archivos de migración basados en las definiciones de entidad actuales

atlas migrate diff nombre_de_la_migracion \
  --dir "file://ent/migrate/migrations" \
  --to "ent://ent/schema" \
  --dev-url "docker://mysql/8/ent"

3. Archivos de migración de la aplicación

Una vez que los archivos de migración están generados, pueden aplicarse a los entornos de desarrollo, pruebas o producción. Normalmente, primero se aplicarían estos archivos de migración a una base de datos de desarrollo o pruebas para asegurarse de que se ejecuten como se espera. Luego, los mismos pasos de migración se ejecutarían en el entorno de producción.

atlas migrate apply \
  --dir "file://ent/migrate/migrations" \
  --url "mysql://root:pass@localhost:3306/example"

Utiliza el comando atlas migrate apply, especificando el directorio de archivos de migración (--dir) y la URL de la base de datos de destino (--url) para aplicar los archivos de migración.