Ent es el ORM de Meta para Go con un enfoque radicalmente diferente: el schema se escribe en código Go puro, no en YAML ni en anotaciones. Ent genera a partir de ese schema un cliente completamente tipado donde los errores de query se detectan en compilación, no en runtime.
Instalación e inicialización
go get entgo.io/ent/cmd/ent go run entgo.io/ent/cmd/ent new Usuario Articulo
Esto crea ent/schema/usuario.go y ent/schema/articulo.go. Tras definir los schemas, generas el cliente con:
go generate ./ent
Definir el schema como código Go
// ent/schema/usuario.go
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
)
type Usuario struct {
ent.Schema
}
func (Usuario) Fields() []ent.Field {
return []ent.Field{
field.String("nombre").NotEmpty(),
field.String("email").Unique(),
field.Int("edad").Positive().Optional(),
field.Bool("activo").Default(true),
field.Time("creado_en").Immutable(),
}
}
func (Usuario) Edges() []ent.Edge {
return []ent.Edge{
edge.To("articulos", Articulo.Type),
}
}
// ent/schema/articulo.go
func (Articulo) Fields() []ent.Field {
return []ent.Field{
field.String("titulo").NotEmpty(),
field.Text("cuerpo"),
field.Bool("publicado").Default(false),
}
}
func (Articulo) Edges() []ent.Edge {
return []ent.Edge{
edge.From("autor", Usuario.Type).Ref("articulos").Unique().Required(),
}
}
Crear el cliente y ejecutar migraciones
package main
import (
"context"
"log"
_ "github.com/go-sql-driver/mysql"
"tumodulo/ent"
)
func main() {
client, err := ent.Open("mysql", "progra7net:pass@tcp(localhost:3306)/progra7net?parseTime=True")
if err != nil {
log.Fatalf("error abriendo cliente: %v", err)
}
defer client.Close()
ctx := context.Background()
if err := client.Schema.Create(ctx); err != nil {
log.Fatalf("error en migración: %v", err)
}
}
CRUD con el cliente tipado
// Crear un usuario
u, err := client.Usuario.Create().
SetNombre("Ana García").
SetEmail("[email protected]").
SetEdad(30).
Save(ctx)
if err != nil {
return fmt.Errorf("error creando usuario: %w", err)
}
fmt.Println("ID del nuevo usuario:", u.ID)
// Consultar con predicados
usuarios, err := client.Usuario.Query().
Where(
usuario.EdadGT(18),
usuario.ActivoEQ(true),
usuario.NombreContains("García"),
).
Order(ent.Asc(usuario.FieldNombre)).
Limit(10).
All(ctx)
// Cargar relaciones (edges)
arts, err := client.Usuario.Query().
Where(usuario.ID(u.ID)).
QueryArticulos().
Where(articulo.PublicadoEQ(true)).
All(ctx)
// Actualizar
u, err = u.Update().
SetEdad(31).
AddArticulos(art).
Save(ctx)
// Eliminar
err = client.Usuario.DeleteOne(u).Exec(ctx)
Hooks: controlar mutaciones
Los hooks interceptan operaciones de escritura antes o después de que ocurran. Son ideales para auditoría, validación o efectos secundarios:
func HookAuditoriaUsuario() ent.Hook {
return func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
// antes de la operación
op := m.Op()
if op == ent.OpCreate {
fmt.Println("creando nuevo usuario")
}
v, err := next.Mutate(ctx, m)
// después de la operación
if err == nil && op == ent.OpDeleteOne {
fmt.Println("usuario eliminado")
}
return v, err
})
}
}
// Registrar el hook
client.Usuario.Use(HookAuditoriaUsuario())
// Hook solo para Create
client.Usuario.Use(hook.On(HookAuditoriaUsuario(), ent.OpCreate))
Transacciones
tx, err := client.Tx(ctx)
if err != nil {
return err
}
u, err := tx.Usuario.Create().
SetNombre("Luis").
SetEmail("[email protected]").
Save(ctx)
if err != nil {
return tx.Rollback()
}
_, err = tx.Articulo.Create().
SetTitulo("Primer artículo").
SetCuerpo("Contenido...").
SetAutor(u).
Save(ctx)
if err != nil {
return tx.Rollback()
}
return tx.Commit()
Ent genera código distinto para cada base de datos (SQLite, MySQL, PostgreSQL, MariaDB) en función del driver que uses. El cliente tipado es lo que diferencia a Ent del resto de ORMs Go: no hay interface{} ni reflexión en las queries del día a día.
