1. Instalación de la herramienta ent

Para instalar la herramienta de generación de código ent, debes seguir estos pasos:

Primero, asegúrate de que tu sistema tenga instalado el entorno de lenguaje Go. Luego, ejecuta el siguiente comando para obtener la herramienta ent:

go get -d entgo.io/ent/cmd/ent

Este comando descargará el código de la herramienta ent, pero no lo compilará e instalará de inmediato. Si deseas instalar ent en el directorio $GOPATH/bin para poder usarlo en cualquier lugar, también necesitarás ejecutar el siguiente comando:

go install entgo.io/ent/cmd/ent

Una vez completada la instalación, puedes verificar si la herramienta ent está correctamente instalada y ver los comandos disponibles e instrucciones mediante el comando ent -h.

2. Inicialización de esquema

2.1 Inicialización de la plantilla usando ent init

Crear un nuevo archivo de esquema es el primer paso para comenzar a usar ent para la generación de código. Puedes inicializar la plantilla de esquema ejecutando el siguiente comando:

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

Este comando creará dos nuevos archivos de esquema: user.go y pet.go, y los colocará en el directorio ent/schema. Si el directorio ent no existe, este comando también lo creará automáticamente.

Ejecutar el comando ent init en el directorio principal del proyecto es una buena práctica, ya que ayuda a mantener la estructura y claridad del directorio del proyecto.

2.2 Estructura del archivo de esquema

En el directorio ent/schema, cada esquema corresponde a un archivo fuente del lenguaje Go. Los archivos de esquema son donde defines el modelo de base de datos, incluyendo los campos y aristas (relaciones).

Por ejemplo, en el archivo user.go, podrías definir un modelo de usuario, incluyendo campos como nombre de usuario y edad, y definir la relación entre usuarios y mascotas. De manera similar, en el archivo pet.go, definirías el modelo de mascota y sus campos relacionados, como el nombre de la mascota, tipo, y la relación entre mascotas y usuarios.

Estos archivos en última instancia serán utilizados por la herramienta ent para generar el código Go correspondiente, incluyendo el código cliente para operaciones de base de datos y operaciones CRUD (Crear, Leer, Actualizar, Borrar).

// ent/schema/user.go
package schema

import (
    "entgo.io/ent"
    "entgo.io/ent/schema/field"
)

// User define el esquema para la entidad Usuario.
type User struct {
    ent.Schema
}

// El método Fields se utiliza para definir los campos de la entidad.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").Unique(),
        field.Int("age").Positive(),
    }
}

// El método Edges se utiliza para definir las asociaciones de la entidad.
func (User) Edges() []ent.Edge {
    // Las asociaciones se explicarán con más detalle en la siguiente sección.
}

Los archivos de esquema de ent utilizan tipos y funciones del lenguaje Go para declarar la estructura del modelo de base de datos, incluyendo los campos y relaciones entre modelos, y utilizan la API proporcionada por el marco ent para definir estas estructuras. Este enfoque hace que ent sea muy intuitivo y fácil de extender, al tiempo que aprovecha la tipificación fuerte del lenguaje Go.

3.1 Ejecución de la generación de código

Ejecutar ent generate para generar código es un paso crucial en el marco ent. Con este comando, ent generará archivos de código Go correspondientes basados en los esquemas definidos, facilitando el trabajo de desarrollo posterior. El comando para ejecutar la generación de código es sencillo:

go generate ./ent

El comando anterior debe ejecutarse en el directorio raíz del proyecto. Cuando se llama a go generate, buscará todos los archivos Go que contengan anotaciones específicas y ejecutará los comandos especificados en las anotaciones. En nuestro ejemplo, este comando especifica el generador de código para ent.

Asegúrate de que la inicialización del esquema y las adiciones de campo necesarias se hayan completado antes de la ejecución. Solo entonces el código generado incluirá todas las partes necesarias.

3.2 Comprendiendo los Activos de Código Generados

Los activos de código generados contienen múltiples componentes, cada uno con funciones diferentes:

  • Objetos Cliente y Tx: Se utilizan para interactuar con el gráfico de datos. El Cliente proporciona métodos para crear transacciones (Tx) o ejecutar operaciones directamente en la base de datos.

  • Constructores CRUD: Para cada tipo de esquema, genera constructores para crear, leer, actualizar y eliminar, simplificando la lógica de operación de la entidad correspondiente.

  • Objeto de Entidad (estructura en Go): Genera las estructuras de Go correspondientes para cada tipo en el esquema, mapeando estas estructuras a las tablas en la base de datos.

  • Paquete de constantes y predicados: Contiene constantes y predicados para interactuar con los constructores.

  • Paquete de migración: Un paquete para la migración de la base de datos, adecuado para dialectos SQL.

  • Paquete de gancho (Hook): Proporciona la funcionalidad para agregar middleware de cambio, permitiendo que la lógica personalizada se ejecute antes o después de crear, actualizar o eliminar entidades.

Al examinar el código generado, puedes obtener una comprensión más profunda de cómo el marco ent automatiza el código de acceso a datos para tus esquemas.

4. Opciones de Generación de Código

El comando ent generate admite varias opciones para personalizar el proceso de generación de código. Puedes consultar todas las opciones de generación admitidas a través del siguiente comando:

ent generate -h

Aquí tienes algunas opciones de línea de comandos comúnmente utilizadas:

  • --feature strings: Extiende la generación de código, agregando funcionalidades adicionales.
  • --header string: Reemplaza el archivo de encabezado de generación de código.
  • --storage string: Especifica el controlador de almacenamiento admitido en la generación de código, con un valor predeterminado de "sql".
  • --target string: Especifica el directorio de destino para la generación de código.
  • --template strings: Ejecuta plantillas adicionales de Go. Admite archivo, directorio y ruta de comodín, por ejemplo: --template file="path/to/file.tmpl".

Estas opciones permiten a los desarrolladores personalizar su proceso de generación de código según diferentes necesidades y preferencias.

5. Configuración de Opciones de Almacenamiento

ent admite la generación de activos de código tanto para dialectos SQL como para Gremlin, siendo el predeterminado el dialecto SQL. Si el proyecto necesita conectarse a una base de datos Gremlin, es necesario configurar el dialecto de base de datos correspondiente. Lo siguiente muestra cómo especificar las opciones de almacenamiento:

ent generate --storage gremlin ./ent/schema

El comando anterior instruye a ent a utilizar el dialecto Gremlin al generar código. Esto garantiza que los activos generados estén adaptados a los requisitos de la base de datos Gremlin, asegurando la compatibilidad con una base de datos de gráficos específica.

6. Uso Avanzado: Paquete entc

6.1 Uso de entc como un Paquete en el Proyecto

entc es el paquete principal utilizado para la generación de código en el marco ent. Además de la herramienta de línea de comandos, entc también se puede integrar en el proyecto como un paquete, lo que permite a los desarrolladores controlar y personalizar el proceso de generación de código dentro del propio código.

Para utilizar entc como un paquete en el proyecto, es necesario crear un archivo llamado entc.go y agregar el siguiente contenido al archivo:

// +build ignore

package main

import (
    "log"
    "entgo.io/ent/entc"
    "entgo.io/ent/entc/gen"
)

func main() {
    if err := entc.Generate("./schema", &gen.Config{}); err != nil {
        log.Fatal("running ent codegen:", err)
    }
}

Al utilizar este enfoque, puedes modificar la estructura gen.Config dentro de la función main para aplicar diferentes opciones de configuración. Llamando a la función entc.Generate según sea necesario, puedes controlar de forma flexible el proceso de generación de código.

6.2 Configuración Detallada de entc

entc proporciona opciones de configuración extensas que permiten a los desarrolladores personalizar el código generado. Por ejemplo, se pueden configurar ganchos personalizados para inspeccionar o modificar el código generado, y se pueden inyectar dependencias externas utilizando la inyección de dependencias.

El siguiente ejemplo demuestra cómo proporcionar ganchos personalizados para la función entc.Generate:

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            HookFunction,
        },
    })
    if err != nil {
        log.Fatalf("ejecutando la generación de código ent: %v", err)
    }
}

// HookFunction es una función de gancho personalizada
func HookFunction(next gen.Generator) gen.Generator {
    return gen.GenerateFunc(func(g *gen.Graph) error {
        // Se puede manejar el modo de gráfico representado por g aquí
        // Por ejemplo, validar la existencia de campos o modificar la estructura
        return next.Generate(g)
    })
}

Además, se pueden agregar dependencias externas utilizando entc.Dependency:

func main() {
    opts := []entc.Option{
        entc.Dependency(
            entc.DependencyType(&http.Client{}),
        ),
        entc.Dependency(
            entc.DependencyName("Writer"),
            entc.DependencyTypeInfo(&field.TypeInfo{
                Ident:   "io.Writer",
                PkgPath: "io",
            }),
        ),
    }
    if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
        log.Fatalf("ejecutando la generación de código ent: %v", err)
    }
}

En este ejemplo, inyectamos http.Client y io.Writer como dependencias en los objetos de cliente generados.

7. Salida de la Descripción del Esquema

En el marco de ent, el comando ent describe se puede utilizar para obtener la descripción del esquema en un formato gráfico. Esto puede ayudar a los desarrolladores a comprender rápidamente las entidades y relaciones existentes.

Ejecute el siguiente comando para obtener la descripción del esquema:

go run -mod=mod entgo.io/ent/cmd/ent describe ./ent/schema

El comando anterior generará una tabla similar a la siguiente, que muestra información como campos, tipos, relaciones, etc. para cada entidad:

Usuario:
    +-------+---------+--------+-----------+ ...
    | Campo |  Tipo   | Único  | Opcional  | ...
    +-------+---------+--------+-----------+ ...
    | id    | int     | false  | false     | ...
    | nombre| string  | true   | false     | ...
    +-------+---------+--------+-----------+ ...
    +-------+--------+---------+-----------+ ...
    | Borde |  Tipo   | Inverso | Relación  | ...
    +-------+--------+---------+-----------+ ...
    | mascotas | Mascota| false  | O2M       | ...
    +-------+--------+---------+-----------+ ...

8. Ganchos de Generación de Código

8.1 Concepto de Ganchos

Los ganchos son funciones middleware que se pueden insertar en el proceso de generación de código de ent, lo que permite insertar lógica personalizada antes y después de generar código. Los ganchos se pueden utilizar para manipular el árbol de sintaxis abstracta (AST) del código generado, realizar validaciones o agregar fragmentos de código adicionales.

8.2 Ejemplo de Uso de Ganchos

Aquí tienes un ejemplo de uso de un gancho para asegurar que todos los campos contengan cierta etiqueta de estructura (por ejemplo, json):

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            EnsureStructTag("json"),
        },
    })
    if err != nil {
        log.Fatalf("ejecutando la generación de código ent: %v", err)
    }
}

// EnsureStructTag asegura que todos los campos en el grafo contengan una etiqueta de estructura específica
func EnsureStructTag(name string) gen.Hook {
    return func(next gen.Generator) gen.Generator {
        return gen.GenerateFunc(func(g *gen.Graph) error {
            for _, node := range g.Nodes {
                for _, field := range node.Fields {
                    tag := reflect.StructTag(field.StructTag)
                    if _, ok := tag.Lookup(name); !ok {
                        return fmt.Errorf("falta la etiqueta de estructura %q para el campo %s.%s", name, node.Name, field.Name)
                    }
                }
            }
            return next.Generate(g)
        })
    }
}

En este ejemplo, antes de generar el código, la función EnsureStructTag verifica cada campo en busca de la etiqueta json. Si un campo carece de esta etiqueta, la generación de código se detendrá y se devolverá un error. Esta es una forma efectiva de mantener la limpieza y consistencia del código.