Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Cuando Ariel lanzó Ent v0.10.0 a finales de enero, introdujo un nuevo motor de migraciones para Ent basado en otro proyecto de código abierto llamado Atlas.
Inicialmente, Atlas admitía un estilo de gestión de esquemas de bases de datos que llamamos "migraciones declarativas". Con las migraciones declarativas, el estado deseado del esquema de la base de datos se proporciona como entrada al motor de migraciones, que planifica y ejecuta un conjunto de acciones para cambiar la base de datos a su estado deseado. Este enfoque se ha popularizado en el campo de la infraestructura nativa en la nube gracias a proyectos como Kubernetes y Terraform. Funciona muy bien en muchos casos, de hecho ha servido muy bien al framework Ent durante los últimos años. Sin embargo, las migraciones de bases de datos son un tema muy delicado, y muchos proyectos requieren un enfoque más controlado.
Por esta razón, la mayoría de las soluciones estándar de la industria, como Flyway, Liquibase, o golang-migrate/migrate (común en el ecosistema de Go), admiten un flujo de trabajo que denominan "migraciones versionadas".
Con las migraciones versionadas (a veces llamadas "migraciones basadas en cambios"), en lugar de describir el estado deseado ("cómo debe verse la base de datos"), describes los cambios en sí mismos ("cómo alcanzar el estado"). La mayoría de las veces esto se hace creando un conjunto de archivos SQL que contienen las sentencias necesarias. A cada archivo se le asigna una versión única y una descripción de los cambios. Herramientas como las mencionadas anteriormente pueden interpretar los archivos de migración y aplicar (algunos de) ellos en el orden correcto para transicionar a la estructura de base de datos deseada.
En esta publicación, quiero mostrar un nuevo tipo de flujo de trabajo de migración que se ha añadido recientemente a Atlas y Ent. Lo llamamos "creación de migraciones versionadas" y es un intento de combinar la simplicidad y expresividad del enfoque declarativo con la seguridad y claridad de las migraciones versionadas. Con la creación de migraciones versionadas, los usuarios siguen declarando su estado deseado y usando el motor de Atlas para planificar una migración segura desde el estado existente al nuevo estado. Sin embargo, en lugar de vincular la planificación y la ejecución, se escribe en un archivo que puede guardarse en control de código fuente, ajustarse manualmente y revisarse en procesos normales de revisión de código.
Como ejemplo, demostraré el flujo de trabajo con golang-migrate/migrate.
Empezando
Lo primero que debes hacer es asegurarte de tener una versión actualizada de Ent:
go get -u entgo.io/ent@master
Hay dos formas de hacer que Ent genere archivos de migración para cambios de esquema. La primera es usar un cliente de Ent instanciado y la segunda es generar los cambios a partir de un gráfico de esquema analizado. Esta publicación tomará el segundo enfoque. Si quieres aprender a usar el primero, puedes consultar la documentación.
Generación de archivos de migración versionados
Como ya hemos activado la función de migraciones versionadas, vamos a crear un esquema pequeño y generar el conjunto inicial de archivos de migración. Considera el siguiente esquema para un proyecto nuevo de Ent:
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("username"),
}
}
// Indexes of the User.
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("username").Unique(),
}
}
Como mencioné antes, queremos usar el gráfico de esquema analizado para calcular la diferencia entre nuestro esquema y la base de datos conectada. Aquí tienes un ejemplo de un contenedor Docker de MySQL (semi)persistente para usar si quieres seguir el ejemplo:
docker run --rm --name ent-versioned-migrations --detach --env MYSQL_ROOT_PASSWORD=pass --env MYSQL_DATABASE=ent -p 3306:3306 mysql
Una vez que hayas terminado, puedes detener el contenedor y eliminar todos los recursos con docker stop ent-versioned-migrations.
Ahora, vamos a crear una pequeña función que cargue el gráfico de esquema y genere los archivos de migración. Crea un nuevo archivo Go
llamado main.go y copia el siguiente contenido:
package main
import (
"context"
"log"
"os"
"ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/schema"
"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// We need a name for the new migration file.
if len(os.Args) < 2 {
log.Fatalln("no name given")
}
// Create a local migration directory.
dir, err := migrate.NewLocalDir("migrations")
if err != nil {
log.Fatalln(err)
}
// Load the graph.
graph, err := entc.LoadGraph("./ent/schema", &gen.Config{})
if err != nil {
log.Fatalln(err)
}
tbls, err := graph.Tables()
if err != nil {
log.Fatalln(err)
}
// Open connection to the database.
drv, err := sql.Open("mysql", "root:pass@tcp(localhost:3306)/ent")
if err != nil {
log.Fatalln(err)
}
// Inspect the current database state and compare it with the graph.
m, err := schema.NewMigrate(drv, schema.WithDir(dir))
if err != nil {
log.Fatalln(err)
}
if err := m.NamedDiff(context.Background(), os.Args[1], tbls...); err != nil {
log.Fatalln(err)
}
}
Ahora solo tenemos que crear el directorio de migraciones y ejecutar el archivo Go anterior:
mkdir migrations
go run -mod=mod main.go initial
Ahora verás dos archivos nuevos en el directorio migrations: <timestamp>_initial.down.sql y <timestamp>_initial.up.sql. Los archivos x.up.sql se utilizan para crear la versión x de la base de datos y los x.down.sql para revertir a la versión anterior.
CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(191) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `user_username` (`username`)) CHARSET utf8mb4 COLLATE utf8mb4_bin;
DROP TABLE `users`;
Aplicar migraciones
Para aplicar estas migraciones en tu base de datos, instala la herramienta golang-migrate/migrate como se describe en su README. Luego ejecuta el siguiente comando para verificar que todo funcione correctamente.
migrate -help
Usage: migrate OPTIONS COMMAND [arg...]
migrate [ -version | -help ]
Options:
-source Location of the migrations (driver://url)
-path Shorthand for -source=file://path
-database Run migrations against this database (driver://url)
-prefetch N Number of migrations to load in advance before executing (default 10)
-lock-timeout N Allow N seconds to acquire database lock (default 15)
-verbose Print verbose logging
-version Print version
-help Print usage
Commands:
create [-ext E] [-dir D] [-seq] [-digits N] [-format] NAME
Create a set of timestamped up/down migrations titled NAME, in directory D with extension E.
Use -seq option to generate sequential up/down migrations with N digits.
Use -format option to specify a Go time format string.
goto V Migrate to version V
up [N] Apply all or N up migrations
down [N] Apply all or N down migrations
drop Drop everything inside database
force V Set version V but don't run migration (ignores dirty state)
version Print current migration version
Ahora podemos ejecutar nuestra migración inicial y sincronizar la base de datos con nuestro esquema:
migrate -source 'file://migrations' -database 'mysql://root:pass@tcp(localhost:3306)/ent' up
<timestamp>/u initial (349.256951ms)
Flujo de trabajo
Para demostrar el flujo de trabajo habitual con migraciones versionadas, editaremos nuestro esquema para generar los cambios de migración correspondientes y crearemos manualmente archivos de migración para poblar la base de datos con datos iniciales. Primero añadiremos un esquema Group y una relación muchos-a-muchos con el esquema User existente, luego crearemos un grupo de administración con un usuario administrador. Realiza estos cambios:
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("username"),
}
}
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.From("groups", Group.Type).
Ref("users"),
}
}
// Indexes of the User.
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("username").Unique(),
}
}
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// Group holds the schema definition for the Group entity.
type Group struct {
ent.Schema
}
// Fields of the Group.
func (Group) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
}
}
// Edges of the Group.
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
}
}
// Indexes of the Group.
func (Group) Indexes() []ent.Index {
return []ent.Index{
index.Fields("name").Unique(),
}
}
Una vez actualizado el esquema, crea un nuevo conjunto de archivos de migración.
go run -mod=mod main.go add_group_schema
Nuevamente aparecerán dos archivos en el directorio migrations: <timestamp>_add_group_schema.down.sql y <timestamp>_add_group_schema.up.sql.
CREATE TABLE `groups` (`id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(191) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `group_name` (`name`)) CHARSET utf8mb4 COLLATE utf8mb4_bin;
CREATE TABLE `group_users` (`group_id` bigint NOT NULL, `user_id` bigint NOT NULL, PRIMARY KEY (`group_id`, `user_id`), CONSTRAINT `group_users_group_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE, CONSTRAINT `group_users_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE) CHARSET utf8mb4 COLLATE utf8mb4_bin;
DROP TABLE `group_users`;
DROP TABLE `groups`;
Ahora puedes editar los archivos generados para añadir los datos iniciales o crear nuevos archivos para ello. Elegiré la segunda opción:
migrate create -format unix -ext sql -dir migrations seed_admin
[...]/ent-versioned-migrations/migrations/<timestamp>_seed_admin.up.sql
[...]/ent-versioned-migrations/migrations/<timestamp>_seed_admin.down.sql
Ahora puedes editar esos archivos y añadir sentencias para crear un Group de administración y un User administrador.
INSERT INTO `groups` (`id`, `name`) VALUES (1, 'Admins');
INSERT INTO `users` (`id`, `username`) VALUES (1, 'admin');
INSERT INTO `group_users` (`group_id`, `user_id`) VALUES (1, 1);
DELETE FROM `group_users` where `group_id` = 1 and `user_id` = 1;
DELETE FROM `groups` where id = 1;
DELETE FROM `users` where id = 1;
Aplica las migraciones nuevamente y habrás terminado:
migrate -source file://migrations -database 'mysql://root:pass@tcp(localhost:3306)/ent' up
<timestamp>/u add_group_schema (417.434415ms)
<timestamp>/u seed_admin (674.189872ms)
Conclusión
En esta entrada hemos demostrado el flujo de trabajo con Ent Versioned Migrations usando golang-migate/migrate. Creamos un esquema de ejemplo, generamos los archivos de migración correspondientes y aprendimos a aplicarlos. Ahora conocemos el flujo básico y cómo añadir migraciones personalizadas.
¿Tienes preguntas? ¿Necesitas ayuda para empezar? Únete a nuestro servidor de Discord o canal de Slack.
- Suscríbete a nuestro Newsletter
- Síguenos en Twitter
- Únete a #ent en Gophers Slack
- Únete al Ent Discord Server



























