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:
//+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:
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:
// ...
// 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:
{{ 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:
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:
// 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:
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.
- Suscríbete a nuestro Newsletter
- Síguenos en Twitter
- Únete a #ent en Gophers Slack
- Únete al Ent Discord Server