Saltar al contenido principal

Generación de esquemas Ent a partir de bases de datos SQL existentes

· 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 unos meses, el proyecto Ent anunció la Iniciativa de Importación de Esquemas, cuyo objetivo es ayudar a cubrir múltiples casos de uso para generar esquemas Ent a partir de recursos externos. Hoy me complace compartir un proyecto en el que he estado trabajando: entimport, una herramienta de línea de comandos importante (juego de palabras intencionado) diseñada para crear esquemas Ent a partir de bases de datos SQL existentes. Esta es una funcionalidad solicitada por la comunidad desde hace tiempo, así que espero que mucha gente la encuentre útil. Puede facilitar la transición de configuraciones existentes desde otros lenguajes u ORM a Ent. También resulta útil en casos donde se desea acceder a los mismos datos desde diferentes plataformas (como para sincronizarlas automáticamente).
La primera versión soporta bases de datos MySQL y PostgreSQL, con algunas limitaciones que se describen más abajo. El soporte para otras bases de datos relacionales como SQLite está en desarrollo.

Primeros pasos

Para que te hagas una idea de cómo funciona entimport, quiero compartir un ejemplo rápido de uso completo con una base de datos MySQL. A grandes rasgos, esto es lo que haremos:

  1. Crear una base de datos y esquema: queremos mostrar cómo entimport puede generar un esquema Ent para una base de datos existente. Primero crearemos una base de datos, luego definiremos algunas tablas que podremos importar a Ent.

  2. Inicializar un proyecto Ent: usaremos la CLI de Ent para crear la estructura de directorios necesaria y un script de generación de esquemas Ent.

  3. Instalar entimport

  4. Ejecutar entimport contra nuestra base de datos demo: a continuación, importaremos el esquema de base de datos que hemos creado a nuestro proyecto Ent.

  5. Explicar cómo usar Ent con nuestros esquemas generados.

Empecemos.

Crear una base de datos

Comenzaremos creando una base de datos. La forma que prefiero hacerlo es usando un contenedor de Docker. Utilizaremos un docker-compose que pasará automáticamente todos los parámetros necesarios al contenedor MySQL.

Inicia el proyecto en un nuevo directorio llamado entimport-example. Crea un archivo llamado docker-compose.yaml y pega el siguiente contenido dentro:

version: "3.7"

services:

mysql8:
platform: linux/amd64
image: mysql
environment:
MYSQL_DATABASE: entimport
MYSQL_ROOT_PASSWORD: pass
healthcheck:
test: mysqladmin ping -ppass
ports:
- "3306:3306"

Este archivo contiene la configuración del servicio para un contenedor Docker de MySQL. Ejecútalo con el siguiente comando:

docker-compose up -d

A continuación, crearemos un esquema simple. Para este ejemplo usaremos una relación entre dos entidades:

  • Usuario

  • Coche

Conéctate a la base de datos usando la shell de MySQL. Puedes hacerlo con el siguiente comando:

Asegúrate de ejecutarlo desde el directorio raíz del proyecto

docker-compose exec mysql8 mysql --database=entimport -ppass
create table users
(
id bigint auto_increment primary key,
age bigint not null,
name varchar(255) not null,
last_name varchar(255) null comment 'surname'
);

create table cars
(
id bigint auto_increment primary key,
model varchar(255) not null,
color varchar(255) not null,
engine_size mediumint not null,
user_id bigint null,
constraint cars_owners foreign key (user_id) references users (id) on delete set null
);

Validemos que hemos creado las tablas mencionadas anteriormente. En tu shell de MySQL, ejecuta:

show tables;
+---------------------+
| Tables_in_entimport |
+---------------------+
| cars |
| users |
+---------------------+

Deberíamos ver dos tablas: users y cars

Inicializar proyecto Ent

Ahora que hemos creado nuestra base de datos y un esquema base para demostrar nuestro ejemplo, necesitamos crear un proyecto Go con Ent. En esta fase explicaré cómo hacerlo. Como eventualmente querremos usar nuestro esquema importado, necesitamos crear la estructura de directorios de Ent.

Inicializa un nuevo proyecto Go dentro de un directorio llamado entimport-example

go mod init entimport-example

Ejecuta Ent Init:

go run -mod=mod entgo.io/ent/cmd/ent new 

El proyecto debería verse así:

├── docker-compose.yaml
├── ent
│ ├── generate.go
│ └── schema
└── go.mod

Instalar entimport

¡Vale, ahora empieza lo divertido! Por fin estamos listos para instalar entimport y verlo en acción.
Empecemos ejecutando entimport:

go run -mod=mod ariga.io/entimport/cmd/entimport -h

Se descargará entimport y el comando mostrará:

Usage of entimport:
-dialect string
database dialect (default "mysql")
-dsn string
data source name (connection information)
-schema-path string
output path for ent schema (default "./ent/schema")
-tables value
comma-separated list of tables to inspect (all if empty)

Ejecutar entimport

¡Ya estamos listos para importar nuestro esquema MySQL a Ent!

Lo haremos con el siguiente comando:

Este comando importará todas las tablas de nuestro esquema. También puedes limitarlo a tablas específicas usando el flag -tables.

go run ariga.io/entimport/cmd/entimport -dialect mysql -dsn "root:pass@tcp(localhost:3306)/entimport"

Como muchas herramientas Unix, entimport no muestra nada cuando se ejecuta correctamente. Para verificar que funcionó, revisaremos el sistema de archivos, concretamente el directorio ent/schema.

├── docker-compose.yaml
├── ent
│ ├── generate.go
│ └── schema
│ ├── car.go
│ └── user.go
├── go.mod
└── go.sum

Veamos qué obtenemos: recordemos que teníamos dos esquemas, users y cars con una relación uno a muchos. Veamos cómo lo gestionó entimport.

entimport-example/ent/schema/user.go
type User struct {
ent.Schema
}

func (User) Fields() []ent.Field {
return []ent.Field{field.Int("id"), field.Int("age"), field.String("name"), field.String("last_name").Optional().Comment("surname")}
}
func (User) Edges() []ent.Edge {
return []ent.Edge{edge.To("cars", Car.Type)}
}
func (User) Annotations() []schema.Annotation {
return nil
}
entimport-example/ent/schema/car.go
type Car struct {
ent.Schema
}

func (Car) Fields() []ent.Field {
return []ent.Field{field.Int("id"), field.String("model"), field.String("color"), field.Int32("engine_size"), field.Int("user_id").Optional()}
}
func (Car) Edges() []ent.Edge {
return []ent.Edge{edge.From("user", User.Type).Ref("cars").Unique().Field("user_id")}
}
func (Car) Annotations() []schema.Annotation {
return nil
}

¡entimport creó con éxito las entidades y su relación!

Hasta aquí todo bien. Ahora probémoslo realmente. Primero debemos generar el esquema de Ent, porque Ent es un ORM primero-esquema que genera código Go para interactuar con diferentes bases de datos.

Para ejecutar la generación de código de Ent:

go generate ./ent

Veamos nuestro directorio ent:

...
├── ent
│ ├── car
│ │ ├── car.go
│ │ └── where.go
...
│ ├── schema
│ │ ├── car.go
│ │ └── user.go
...
│ ├── user
│ │ ├── user.go
│ │ └── where.go
...

Ejemplo con Ent

Ejecutemos un ejemplo rápido para verificar que nuestro esquema funciona:

Crea un archivo llamado example.go en la raíz del proyecto con este contenido:

Esta parte del ejemplo está disponible aquí

entimport-example/example.go
package main

import (
"context"
"fmt"
"log"

"entimport-example/ent"

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

func main() {
client, err := ent.Open(dialect.MySQL, "root:pass@tcp(localhost:3306)/entimport?parseTime=True")
if err != nil {
log.Fatalf("failed opening connection to mysql: %v", err)
}
defer client.Close()
ctx := context.Background()
example(ctx, client)
}

Intentemos añadir un usuario. Escribe este código al final del archivo:

entimport-example/example.go
func example(ctx context.Context, client *ent.Client) {
// Create a User.
zeev := client.User.
Create().
SetAge(33).
SetName("Zeev").
SetLastName("Manilovich").
SaveX(ctx)
fmt.Println("User created:", zeev)
}

Luego ejecuta:

go run example.go

Debería mostrar:

# User created: User(id=1, age=33, name=Zeev, last_name=Manilovich)

Comprobemos en la base de datos si realmente se añadió el usuario:

SELECT *
FROM users
WHERE name = 'Zeev';

+--+---+----+----------+
|id|age|name|last_name |
+--+---+----+----------+
|1 |33 |Zeev|Manilovich|
+--+---+----+----------+

¡Genial! Ahora juguemos un poco más con Ent añadiendo relaciones. Añade este código al final de la función example():

Asegúrate de añadir "entimport-example/ent/user" en la declaración de import()

entimport-example/example.go
// Create Car.
vw := client.Car.
Create().
SetModel("volkswagen").
SetColor("blue").
SetEngineSize(1400).
SaveX(ctx)
fmt.Println("First car created:", vw)

// Update the user - add the car relation.
client.User.Update().Where(user.ID(zeev.ID)).AddCars(vw).SaveX(ctx)

// Query all cars that belong to the user.
cars := zeev.QueryCars().AllX(ctx)
fmt.Println("User cars:", cars)

// Create a second Car.
delorean := client.Car.
Create().
SetModel("delorean").
SetColor("silver").
SetEngineSize(9999).
SaveX(ctx)
fmt.Println("Second car created:", delorean)

// Update the user - add another car relation.
client.User.Update().Where(user.ID(zeev.ID)).AddCars(delorean).SaveX(ctx)

// Traverse the sub-graph.
cars = delorean.
QueryUser().
QueryCars().
AllX(ctx)
fmt.Println("User cars:", cars)

Esta parte del ejemplo está disponible aquí

Ahora haz: go run example.go.
Tras ejecutar el código anterior, la base de datos debería contener un usuario con 2 coches en una relación uno a muchos.

SELECT *
FROM users;

+--+---+----+----------+
|id|age|name|last_name |
+--+---+----+----------+
|1 |33 |Zeev|Manilovich|
+--+---+----+----------+

SELECT *
FROM cars;

+--+----------+------+-----------+-------+
|id|model |color |engine_size|user_id|
+--+----------+------+-----------+-------+
|1 |volkswagen|blue |1400 |1 |
|2 |delorean |silver|9999 |1 |
+--+----------+------+-----------+-------+

Sincronizar cambios en la BD

Como queremos mantener sincronizada la base de datos, necesitamos que entimport pueda modificar el esquema tras cambios en la BD. Veamos cómo funciona.

Ejecuta este código SQL para añadir una columna phone con índice unique a la tabla users:

alter table users
add phone varchar(255) null;

create unique index users_phone_uindex
on users (phone);

La tabla debería verse así:

describe users;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| age | bigint | NO | | NULL | |
| name | varchar(255) | NO | | NULL | |
| last_name | varchar(255) | YES | | NULL | |
| phone | varchar(255) | YES | UNI | NULL | |
+-----------+--------------+------+-----+---------+----------------+

Ahora ejecutemos entimport nuevamente para obtener el esquema actualizado:

go run -mod=mod ariga.io/entimport/cmd/entimport -dialect mysql -dsn "root:pass@tcp(localhost:3306)/entimport"

Podemos ver que el archivo user.go cambió:

entimport-example/ent/schema/user.go
func (User) Fields() []ent.Field {
return []ent.Field{field.Int("id"), ..., field.String("phone").Optional().Unique()}
}

Ahora podemos ejecutar go generate ./ent nuevamente y usar el nuevo esquema para añadir un phone a la entidad User.

Planes futuros

Como mencioné antes, esta versión inicial soporta bases de datos MySQL y PostgreSQL.
También admite todos los tipos de relaciones SQL. Tengo planes para mejorar la herramienta añadiendo características como campos faltantes de PostgreSQL, valores por defecto y más.

Conclusión

En esta publicación, he presentado entimport, una herramienta muy esperada y solicitada en múltiples ocasiones por la comunidad de Ent. He mostrado un ejemplo práctico de cómo usarla junto con Ent. Esta herramienta es otra incorporación a las utilidades de importación de esquemas de Ent, diseñadas para facilitar aún más la integración de Ent. Para debates y soporte, abre un issue. El ejemplo completo puede encontrarse aquí. ¡Espero que esta publicación te haya resultado útil!

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