1. Introducción a ent
Ent es un marco de entidades desarrollado por Facebook específicamente para el lenguaje Go. Simplifica el proceso de construcción y mantenimiento de aplicaciones de modelo de datos a gran escala. El marco de ent sigue principalmente los siguientes principios:
- Modelar fácilmente el esquema de la base de datos como una estructura de grafo.
- Definir el esquema en forma de código en el lenguaje Go.
- Implementar tipos estáticos basados en generación de código.
- Es muy simple escribir consultas de base de datos y recorrer grafos.
- Fácil de extender y personalizar usando plantillas de Go.
2. Configuración del entorno
Para comenzar a usar el marco de ent, asegúrate de que el lenguaje Go esté instalado en tu entorno de desarrollo.
Si el directorio de tu proyecto está fuera de GOPATH
, o si no estás familiarizado con GOPATH
, puedes usar el siguiente comando para crear un nuevo proyecto de módulo Go:
go mod init entdemo
Esto inicializará un nuevo módulo Go y creará un nuevo archivo go.mod
para tu proyecto entdemo
.
3. Definición del primer esquema
3.1. Crear esquema usando ent CLI
Primero, debes ejecutar el siguiente comando en el directorio raíz de tu proyecto para crear un esquema llamado User utilizando la herramienta ent CLI:
go run -mod=mod entgo.io/ent/cmd/ent new User
El comando anterior generará el esquema User en el directorio entdemo/ent/schema/
:
Archivo entdemo/ent/schema/user.go
:
package schema
import "entgo.io/ent"
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}
// Fields of the User.
func (User) Fields() []ent.Field {
return nil
}
// Edges of the User.
func (User) Edges() []ent.Edge {
return nil
}
3.2. Agregar campos
Luego, necesitamos agregar definiciones de campo al Esquema de Usuario. A continuación, se muestra un ejemplo de cómo agregar dos campos a la entidad de Usuario.
Archivo modificado entdemo/ent/schema/user.go
:
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age").
Positive(),
field.String("name").
Default("unknown"),
}
}
Este fragmento de código define dos campos para el modelo de Usuario: age
y name
, donde age
es un entero positivo y name
es una cadena con un valor predeterminado de "unknown".
3.3. Generar entidades de base de datos
Después de definir el esquema, debes ejecutar el comando go generate
para generar la lógica de acceso a la base de datos subyacente.
Ejecuta el siguiente comando en el directorio raíz de tu proyecto:
go generate ./ent
Este comando generará el código Go correspondiente basado en el esquema definido previamente, lo que resultará en la siguiente estructura de archivos:
ent
├── client.go
├── config.go
├── context.go
├── ent.go
├── generate.go
├── mutation.go
... (varios archivos omitidos por brevedad)
├── schema
│ └── user.go
├── tx.go
├── user
│ ├── user.go
│ └── where.go
├── user.go
├── user_create.go
├── user_delete.go
├── user_query.go
└── user_update.go
4.1. Inicializando la conexión a la base de datos
Para establecer una conexión a la base de datos MySQL, podemos usar la función Open
proporcionada por el marco de trabajo ent
. Primero, importa el controlador de MySQL y luego proporciona la cadena de conexión correcta para inicializar la conexión a la base de datos.
package main
import (
"context"
"log"
"entdemo/ent"
_ "github.com/go-sql-driver/mysql" // Importa el controlador de MySQL
)
func main() {
// Usa ent.Open para establecer una conexión con la base de datos MySQL.
// Recuerda reemplazar los marcadores de posición "tu_nombre_de_usuario", "tu_contraseña" y "tu_base_de_datos" a continuación.
client, err := ent.Open("mysql", "tu_nombre_de_usuario:tu_contraseña@tcp(localhost:3306)/tu_base_de_datos?parseTime=True")
if err != nil {
log.Fatalf("error al abrir la conexión a mysql: %v", err)
}
defer client.Close()
// Ejecuta la herramienta de migración automática
ctx := context.Background()
if err := client.Schema.Create(ctx); err != nil {
log.Fatalf("error al crear los recursos del esquema: %v", err)
}
// Aquí se pueden escribir lógicas comerciales adicionales
}
4.2. Creación de entidades
Crear una entidad de Usuario implica construir un nuevo objeto de entidad y persistirlo en la base de datos utilizando el método Save
o SaveX
. El siguiente código demuestra cómo crear una nueva entidad de Usuario e inicializar dos campos age
y name
.
// La función CreateUser se utiliza para crear una nueva entidad de Usuario
func CreateUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
// Usa client.User.Create() para construir la solicitud de creación de un Usuario,
// luego encadena los métodos SetAge y SetName para establecer los valores de los campos de la entidad.
u, err := client.User.
Create().
SetAge(30). // Establece la edad del usuario
SetName("a8m"). // Establece el nombre del usuario
Save(ctx) // Llama a Save para guardar la entidad en la base de datos
if err != nil {
return nil, fmt.Errorf("error al crear el usuario: %w", err)
}
log.Println("usuario creado: ", u)
return u, nil
}
En la función main
, puedes llamar a la función CreateUser
para crear una nueva entidad de usuario.
func main() {
// ...Código omitido de establecimiento de conexión a la base de datos
// Crea una entidad de usuario
u, err := CreateUser(ctx, client)
if err != nil {
log.Fatalf("error al crear el usuario: %v", err)
}
log.Printf("usuario creado: %#v\n", u)
}
4.3. Consulta de entidades
Para consultar entidades, podemos usar el generador de consultas generado por ent
. El siguiente código demuestra cómo consultar un usuario llamado "a8m":
// La función QueryUser se utiliza para consultar la entidad de usuario con un nombre especificado
func QueryUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
// Usa client.User.Query() para construir la consulta para el Usuario,
// luego encadena el método Where para agregar condiciones de consulta, como consultar por nombre de usuario
u, err := client.User.
Query().
Where(user.NameEQ("a8m")). // Agrega condición de consulta, en este caso, el nombre es "a8m"
Only(ctx) // El método Only indica que solo se espera un resultado
if err != nil {
return nil, fmt.Errorf("error al consultar el usuario: %w", err)
}
log.Println("usuario devuelto: ", u)
return u, nil
}
En la función main
, puedes llamar a la función QueryUser
para consultar la entidad de usuario.
func main() {
// ...Código omitido de establecimiento de conexión a la base de datos y creación de usuario
// Consulta la entidad de usuario
u, err := QueryUser(ctx, client)
if err != nil {
log.Fatalf("error al consultar el usuario: %v", err)
}
log.Printf("usuario consultado: %#v\n", u)
}
5.1. Comprender Bordes e Bordes Inversos
En el marco ent
, el modelo de datos se visualiza como una estructura de grafo, donde las entidades representan nodos en el grafo y las relaciones entre entidades se representan mediante bordes. Un borde es una conexión de una entidad a otra, por ejemplo, un Usuario
puede poseer varios Coches
.
Los Bordes Inversos son referencias inversas a los bordes, representando lógicamente la relación inversa entre entidades, pero sin crear una nueva relación en la base de datos. Por ejemplo, a través del borde inverso de un Coche
, podemos encontrar el Usuario
que posee este coche.
La importancia clave de los bordes y bordes inversos radica en hacer que la navegación entre entidades asociadas sea muy intuitiva y sencilla.
Consejo: En
ent
, los bordes corresponden a claves externas de base de datos tradicionales y se utilizan para definir relaciones entre tablas.
5.2. Definir Bordes en el Esquema
Primero, usaremos la CLI de ent
para crear el esquema inicial para Coche
y Grupo
:
go run -mod=mod entgo.io/ent/cmd/ent new Car Group
A continuación, en el esquema de Usuario
, definimos el borde con Coche
para representar la relación entre usuarios y coches. Podemos agregar un borde coches
que apunte al tipo Coche
en la entidad de usuario, indicando que un usuario puede tener varios coches:
// entdemo/ent/schema/user.go
// Bordes del Usuario.
func (Usuario) Bordes() []ent.Borde {
return []ent.Borde{
borde.Hacia("coches", Coche.Tipo),
}
}
Después de definir los bordes, necesitamos ejecutar nuevamente go generate ./ent
para generar el código correspondiente.
5.3. Operar en Datos de Borde
Crear coches asociados con un usuario es un proceso sencillo. Dada una entidad de usuario, podemos crear una nueva entidad de coche y asociarla con el usuario:
import (
"context"
"log"
"entdemo/ent"
// Asegúrate de importar la definición del esquema para Coche
_ "entdemo/ent/schema"
)
func CrearCochesParaUsuario(ctx context.Context, cliente *ent.Client, IDUsuario int) error {
usuario, err := cliente.Usuario.Obtener(ctx, IDUsuario)
if err != nil {
log.Fatalf("error al obtener el usuario: %v", err)
return err
}
// Crear un coche nuevo y asociarlo con el usuario
_, err = cliente.Coche.
Crear().
EstablecerModelo("Tesla").
EstablecerRegistradoEn(time.Now()).
EstablecerPropietario(usuario).
Guardar(ctx)
if err != nil {
log.Fatalf("error al crear el coche para el usuario: %v", err)
return err
}
log.Println("se creó el coche y se asoció con el usuario")
return nil
}
De manera similar, consultar los coches de un usuario es sencillo. Si queremos recuperar una lista de todos los coches que posee un usuario, podemos hacer lo siguiente:
func ConsultarCochesDeUsuario(ctx context.Context, cliente *ent.Client, IDUsuario int) error {
usuario, err := cliente.Usuario.Obtener(ctx, IDUsuario)
if err != nil {
log.Fatalf("error al obtener el usuario: %v", err)
return err
}
// Consultar todos los coches que posee el usuario
coches, err := usuario.ConsultarCoches().Todos(ctx)
if err != nil {
log.Fatalf("error al consultar los coches: %v", err)
return err
}
for _, coche := range coches {
log.Printf("coche: %v, modelo: %v", coche.ID, coche.Modelo)
}
return nil
}
A través de los pasos anteriores, no solo hemos aprendido a definir bordes en el esquema, sino que también hemos demostrado cómo crear y consultar datos relacionados con los bordes.
6. Travesía y Consulta de Grafos
6.1. Comprender Estructuras de Grafo
En ent
, las estructuras de grafo se representan mediante entidades y los bordes entre ellas. Cada entidad es equivalente a un nodo en el grafo, y las relaciones entre entidades se representan mediante bordes, que pueden ser uno a uno, uno a muchos, muchos a muchos, etc. Esta estructura de grafo hace que las consultas y operaciones complejas en una base de datos relacional sean simples e intuitivas.
6.2. Recorrido de Estructuras de Grafos
Escribir código de recorrido de grafos implica principalmente consultar y asociar datos a través de las aristas entre entidades. A continuación, se muestra un ejemplo sencillo que demuestra cómo recorrer la estructura de grafo en ent
:
import (
"context"
"log"
"entdemo/ent"
)
// GraphTraversal es un ejemplo de recorrido de la estructura de grafo
func GraphTraversal(ctx context.Context, client *ent.Client) error {
// Consultar al usuario llamado "Ariel"
a8m, err := client.User.Query().Where(user.NameEQ("Ariel")).Only(ctx)
if err != nil {
log.Fatalf("Error al consultar al usuario: %v", err)
return err
}
// Recorrer todos los coches pertenecientes a Ariel
cars, err := a8m.QueryCars().All(ctx)
if err != nil {
log.Fatalf("Error al consultar los coches: %v", err)
return err
}
for _, car := range cars {
log.Printf("Ariel tiene un coche con modelo: %s", car.Model)
}
// Recorrer todos los grupos de los que Ariel es miembro
groups, err := a8m.QueryGroups().All(ctx)
if err != nil {
log.Fatalf("Error al consultar los grupos: %v", err)
return err
}
for _, g := range groups {
log.Printf("Ariel es miembro del grupo: %s", g.Name)
}
return nil
}
El código anterior es un ejemplo básico de recorrido de grafos, que primero consulta a un usuario y luego recorre los coches y grupos del usuario.
7. Visualización del Esquema de la Base de Datos
7.1. Instalación de la Herramienta Atlas
Para visualizar el esquema de la base de datos generado por ent
, podemos utilizar la herramienta Atlas. Los pasos de instalación de Atlas son muy sencillos. Por ejemplo, en macOS, puedes instalarlo usando brew
:
brew install ariga/tap/atlas
Nota: Atlas es una herramienta de migración de bases de datos universal que puede manejar el control de versiones de la estructura de tablas para varias bases de datos. Se proporcionará una introducción detallada a Atlas en capítulos posteriores.
7.2. Generación de ERD y Esquema SQL
Usar Atlas para ver y exportar esquemas es muy sencillo. Después de instalar Atlas, puedes utilizar el siguiente comando para ver el Diagrama de Relación de Entidades (ERD):
atlas schema inspect -d [database_dsn] --format dot
O generar directamente el Esquema SQL:
atlas schema inspect -d [database_dsn] --format sql
Donde [database_dsn]
apunta al nombre de origen de datos (DSN) de tu base de datos. Por ejemplo, para una base de datos SQLite, podría ser:
atlas schema inspect -d "sqlite://file:ent.db?mode=memory&cache=share" --format dot
La salida generada por estos comandos se puede transformar aún más en vistas o documentos utilizando las herramientas respectivas.
8. Migración de Esquema
8.1. Migración Automática y Migración Versionada
ent admite dos estrategias de migración de esquema: migración automática y migración versionada. La migración automática es el proceso de inspeccionar y aplicar cambios de esquema en tiempo de ejecución, adecuado para desarrollo y pruebas. La migración versionada implica generar scripts de migración y requiere una revisión cuidadosa y pruebas antes de la implementación en producción.
Consejo: Para la migración automática, consulta el contenido en la sección 4.1.
8.2. Realización de Migración Versionada
El proceso de migración versionada implica generar archivos de migración a través de Atlas. A continuación se muestran los comandos relevantes:
Para generar archivos de migración:
atlas migrate diff -d ent/schema/path --dir migrations/dir
Posteriormente, estos archivos de migración se pueden aplicar a la base de datos:
atlas migrate apply -d migrations/dir --url database_dsn
Siguiendo este proceso, puedes mantener un historial de migraciones de la base de datos en el sistema de control de versiones y garantizar una revisión exhaustiva antes de cada migración.
Consejo: Consulta el código de muestra en https://github.com/ent/ent/tree/master/examples/start