Saltar al contenido principal

Extender Ent con la API de Extensiones

· 7 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, Ariel realizó una contribución silenciosa pero de gran impacto al núcleo de Ent: la API de Extensiones. Aunque Ent ya contaba con capacidades de extensión (como Hooks de generación de código, Plantillas externas y Anotaciones) desde hace tiempo, no existía una forma conveniente de agrupar todos estos componentes en una unidad coherente y autocontenida. La API de Extensiones que analizamos en esta publicación logra precisamente eso.

Muchos ecosistemas de código abierto prosperan específicamente porque ofrecen a los desarrolladores una forma sencilla y estructurada de extender un sistema central pequeño. Se ha criticado mucho al ecosistema Node.js (incluso por su creador original Ryan Dahl), pero es difícil argumentar que la facilidad para publicar y consumir nuevos módulos npm no facilitó la explosión de su popularidad. En mi blog personal he analizado cómo funciona el sistema de plugins de protoc y cómo eso hizo prosperar el ecosistema Protobuf. En resumen, los ecosistemas solo se crean bajo diseños modulares.

En la publicación de hoy, exploraremos la API de Extension de Ent mediante un ejemplo práctico.

Empezando

La API de Extensiones solo funciona en proyectos que utilizan la generación de código de Ent como paquete de Go. Para configurarlo, después de inicializar tu proyecto, crea un archivo llamado ent/entc.go:

ent/entc.go
//+build ignore

package main

import (
"log"

"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
"entgo.io/ent/schema/field"
)

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

A continuación, modifica ent/generate.go para invocar nuestro archivo entc:

ent/generate.go
package ent

//go:generate go run entc.go

Creando nuestra extensión

Todas las extensiones deben implementar la interfaz Extension:

type Extension interface {
// Hooks holds an optional list of Hooks to apply
// on the graph before/after the code-generation.
Hooks() []gen.Hook
// Annotations injects global annotations to the gen.Config object that
// can be accessed globally in all templates. Unlike schema annotations,
// being serializable to JSON raw value is not mandatory.
//
// {{- with $.Config.Annotations.GQL }}
// {{/* Annotation usage goes here. */}}
// {{- end }}
//
Annotations() []Annotation
// Templates specifies a list of alternative templates
// to execute or to override the default.
Templates() []*gen.Template
// Options specifies a list of entc.Options to evaluate on
// the gen.Config before executing the code generation.
Options() []Option
}

Para simplificar el desarrollo de nuevas extensiones, los desarrolladores pueden incrustar entc.DefaultExtension para crear extensiones sin implementar todos los métodos. En entc.go, añade:

ent/entc.go
// ...

// GreetExtension implements entc.Extension.
type GreetExtension {
entc.DefaultExtension
}

Actualmente, nuestra extensión no hace nada. Ahora, conectémosla a nuestra configuración de generación de código. En entc.go, añade nuestra nueva extensión a la invocación de entc.Generate:

err := entc.Generate("./schema", &gen.Config{}, entc.Extensions(&GreetExtension{})

Añadiendo plantillas

Las plantillas externas pueden agruparse en extensiones para mejorar la funcionalidad central de generación de código de Ent. En nuestro ejemplo práctico, nuestro objetivo es añadir a cada entidad un método generado llamado Greet que devuelva un saludo con el nombre del tipo cuando se invoque. Buscamos algo como:

func (u *User) Greet() string {
return "Greetings, User"
}

Para hacer esto, añadamos un nuevo archivo de plantilla externa y coloquémoslo en ent/templates/greet.tmpl:

ent/templates/greet.tmpl
{{ define "greet" }}

{{/* Add the base header for the generated file */}}
{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}

{{/* Loop over all nodes and add the Greet method */}}
{{ range $n := $.Nodes }}
{{ $receiver := $n.Receiver }}
func ({{ $receiver }} *{{ $n.Name }}) Greet() string {
return "Greetings, {{ $n.Name }}"
}
{{ end }}
{{ end }}

A continuación, implementemos el método Templates:

ent/entc.go
func (*GreetExtension) Templates() []*gen.Template {
return []*gen.Template{
gen.MustParse(gen.NewTemplate("greet").ParseFiles("templates/greet.tmpl")),
}
}

Ahora probemos nuestra extensión. Añade un nuevo esquema para el tipo User en un archivo llamado ent/schema/user.go:

package schema

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

// 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("email_address").
Unique(),
}
}

Luego ejecuta:

go generate ./...

Observa que se creó un nuevo archivo, ent/greet.go, que contiene:

ent/greet.go
// Code generated by ent, DO NOT EDIT.

package ent

func (u *User) Greet() string {
return "Greetings, User"
}

¡Genial! Nuestra extensión fue invocada desde la generación de código de Ent y produjo el código que queríamos para nuestro esquema.

Añadiendo anotaciones

Las anotaciones proporcionan una forma de ofrecer a los usuarios de nuestra extensión una API para modificar el comportamiento de la lógica de generación de código. Para añadir anotaciones a nuestra extensión, implementamos el método Annotations. Supongamos que para nuestra GreetExtension queremos permitir a los usuarios configurar la palabra de saludo en el código generado:

// GreetingWord implements entc.Annotation
type GreetingWord string

func (GreetingWord) Name() string {
return "GreetingWord"
}

Primero, añadimos un campo word a nuestra estructura GreetExtension:

type GreetExtension struct {
entc.DefaultExtension
Word GreetingWord
}

Luego, implementamos el método Annotations:

func (s *GreetExtension) Annotations() []entc.Annotation {
return []entc.Annotation{
s.Word,
}
}

Ahora, desde nuestras plantillas podemos acceder a la anotación GreetingWord. Modificamos ent/templates/greet.tmpl para usar nuestra nueva anotación:

func ({{ $receiver }} *{{ $n.Name }}) Greet() string {
return "{{ $.Annotations.GreetingWord }}, {{ $n.Name }}"
}

A continuación, modificamos la configuración de generación de código para establecer la anotación GreetingWord:

"ent/entc.go
err := entc.Generate("./schema",
&gen.Config{},
entc.Extensions(&GreetExtension{
Word: GreetingWord("Shalom"),
}),
)

Para ver cómo nuestra anotación controla el código generado, volvemos a ejecutar:

go generate ./...

Finalmente, observamos que el archivo generado ent/greet.go se actualizó:

func (u *User) Greet() string {
return "Shalom, User"
}

¡Hurra! ¡Hemos añadido una opción para usar una anotación que controla la palabra de saludo en el método generado Greet!

Más posibilidades

Además de plantillas y anotaciones, la API de Extensiones permite empaquetar gen.Hooks y entc.Options en extensiones para controlar aún más el comportamiento de la generación de código. En este post no discutiremos estas posibilidades, pero si estás interesado en usarlas, visita la documentación.

Conclusión

En este post hemos explorado mediante un ejemplo sencillo cómo usar la API Extension para crear nuevas extensiones de generación de código en Ent. Como mencionamos anteriormente, el diseño modular que permite a cualquiera extender la funcionalidad central del software es crucial para el éxito de cualquier ecosistema. Estamos viendo cómo esta afirmación comienza a materializarse en la comunidad de Ent. Aquí tienes una lista de algunos proyectos interesantes que usan la API de Extensiones:

  • elk - una extensión para generar endpoints REST a partir de esquemas Ent.

  • entgql - genera servidores GraphQL a partir de esquemas Ent.

  • entviz - genera diagramas ER a partir de esquemas Ent.

¿Y tú? ¿Tienes una idea para una extensión útil de Ent? Espero que este post haya demostrado que con la nueva API de Extensiones, no es una tarea difícil.

¿Tienes preguntas? ¿Necesitas ayuda para empezar? Únete a nuestro servidor de Discord o canal de Slack.

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