Saltar al contenido principal

Generación de la Especificación OpenAPI con Ent

· 8 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 →

En un artículo anterior, te presentamos elk, una extensión de Ent que permite generar una API HTTP CRUD completamente funcional en Go a partir de tu esquema. En el artículo de hoy me gustaría presentarte una nueva y reluciente característica que ha llegado recientemente a elk: un generador totalmente compatible de la Especificación OpenAPI (OAS).

OAS (anteriormente conocida como Swagger Specification) es una especificación técnica que define una descripción estándar de interfaz, independiente del lenguaje, para APIs REST. Esto permite tanto a humanos como a herramientas automatizadas comprender el servicio descrito sin necesidad del código fuente o documentación adicional. Combinado con las herramientas Swagger, puedes generar código boilerplate tanto para servidor como para cliente en más de 20 lenguajes, simplemente pasando el archivo OAS.

Empezando

El primer paso es añadir el paquete elk a tu proyecto:

go get github.com/masseelch/elk@latest

elk utiliza la API de Extensiones de Ent para integrarse con la generación de código de Ent. Esto requiere que usemos el paquete entc (ent codegen) como se describe aquí para generar el código de nuestro proyecto. Sigue estos dos pasos para activarlo y configurar Ent para trabajar con la extensión elk:

1. Crea un nuevo archivo Go llamado ent/entc.go y pega el siguiente contenido:

// +build ignore

package main

import (
"log"

"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
"github.com/masseelch/elk"
)

func main() {
ex, err := elk.NewExtension(
elk.GenerateSpec("openapi.json"),
)
if err != nil {
log.Fatalf("creating elk extension: %v", err)
}
err = entc.Generate("./schema", &gen.Config{}, entc.Extensions(ex))
if err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}

2. Edita el archivo ent/generate.go para ejecutar el archivo ent/entc.go:

package ent

//go:generate go run -mod=mod entc.go

¡Con estos pasos completados, todo está listo para generar un archivo OAS desde tu esquema! Si eres nuevo en Ent y quieres aprender más sobre cómo conectar con diferentes tipos de bases de datos, ejecutar migraciones o trabajar con entidades, dirígete al Tutorial de Configuración.

Generar un archivo OAS

El primer paso hacia nuestro archivo OAS es crear un gráfico de esquema Ent:

go run -mod=mod entgo.io/ent/cmd/ent new Fridge Compartment Item

Para demostrar las capacidades de generación OAS de elk, construiremos juntos una aplicación de ejemplo. Supongamos que tengo varios frigoríficos con múltiples compartimentos, y mi pareja y yo queremos conocer su contenido en todo momento. Para proporcionarnos esta información increíblemente útil, crearemos un servidor en Go con una API RESTful. Para facilitar la creación de aplicaciones cliente que puedan comunicarse con nuestro servidor, crearemos un archivo de Especificación OpenAPI que describa su API. Una vez que lo tengamos, ¡podremos construir un frontend para gestionar frigoríficos y contenidos en el lenguaje que elijamos usando Swagger Codegen! Puedes encontrar un ejemplo que usa docker para generar un cliente aquí.

Creemos nuestro esquema:

ent/fridge.go
package schema

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

// Fridge holds the schema definition for the Fridge entity.
type Fridge struct {
ent.Schema
}

// Fields of the Fridge.
func (Fridge) Fields() []ent.Field {
return []ent.Field{
field.String("title"),
}
}

// Edges of the Fridge.
func (Fridge) Edges() []ent.Edge {
return []ent.Edge{
edge.To("compartments", Compartment.Type),
}
}
ent/compartment.go
package schema

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

// Compartment holds the schema definition for the Compartment entity.
type Compartment struct {
ent.Schema
}

// Fields of the Compartment.
func (Compartment) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
}
}

// Edges of the Compartment.
func (Compartment) Edges() []ent.Edge {
return []ent.Edge{
edge.From("fridge", Fridge.Type).
Ref("compartments").
Unique(),
edge.To("contents", Item.Type),
}
}
ent/item.go
package schema

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

// Item holds the schema definition for the Item entity.
type Item struct {
ent.Schema
}

// Fields of the Item.
func (Item) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
}
}

// Edges of the Item.
func (Item) Edges() []ent.Edge {
return []ent.Edge{
edge.From("compartment", Compartment.Type).
Ref("contents").
Unique(),
}
}

Ahora, generemos el código de Ent y el archivo OAS.

go generate ./...

Además de los archivos que Ent genera normalmente, se ha creado otro archivo llamado openapi.json. Copia su contenido y pégalo en el Editor Swagger. Deberías ver tres grupos: Compartment, Item y Fridge.

Swagger Editor Example

Swagger Editor Example

Si abres la pestaña de operación POST en el grupo Fridge, verás una descripción de los datos esperados en la solicitud y todas las respuestas posibles. ¡Genial!

POST operation on Fridge

POST operation on Fridge

Configuración básica

La descripción de nuestra API aún no refleja su propósito. ¡Vamos a cambiarlo! elk ofrece constructores de configuración fáciles de usar para personalizar el archivo OAS generado. Abre ent/entc.go y actualiza el título y la descripción de nuestra Fridge API:

ent/entc.go
//go:build ignore
// +build ignore

package main

import (
"log"

"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
"github.com/masseelch/elk"
)

func main() {
ex, err := elk.NewExtension(
elk.GenerateSpec(
"openapi.json",
// It is a Content-Management-System ...
elk.SpecTitle("Fridge CMS"),
// You can use CommonMark syntax (https://commonmark.org/).
elk.SpecDescription("API to manage fridges and their cooled contents. **ICY!**"),
elk.SpecVersion("0.0.1"),
),
)
if err != nil {
log.Fatalf("creating elk extension: %v", err)
}
err = entc.Generate("./schema", &gen.Config{}, entc.Extensions(ex))
if err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}

Al volver a ejecutar el generador de código se creará un archivo OAS actualizado que puedes copiar y pegar en el Swagger Editor.

Updated API Info

Updated API Info

Configuración de operaciones

No queremos exponer endpoints para eliminar frigoríficos (¿en serio, a quién se le ocurriría?). Afortunadamente, elk nos permite configurar qué endpoints generar y cuáles ignorar. La política predeterminada de elk es exponer todas las rutas. Puedes cambiar este comportamiento para no exponer ninguna ruta excepto las solicitadas explícitamente, o simplemente indicar a elk que excluya la operación DELETE en Fridge usando una elk.SchemaAnnotation:

ent/schema/fridge.go
// Annotations of the Fridge.
func (Fridge) Annotations() []schema.Annotation {
return []schema.Annotation{
elk.DeletePolicy(elk.Exclude),
}
}

¡Y voilà! La operación DELETE ha desaparecido.

DELETE operation is gone

DELETE operation is gone

Para más información sobre cómo funcionan las políticas de elk y lo que puedes hacer con ellas, consulta la godoc.

Extender la especificación

Lo más interesante en este ejemplo sería conocer el contenido actual de un frigorífico. Puedes personalizar el OAS generado hasta donde desees usando Hooks. Sin embargo, esto excedería el alcance de esta publicación. Un ejemplo de cómo añadir un endpoint fridges/{id}/contents al archivo OAS generado puede encontrarse aquí.

Generar un servidor compatible con OAS

Al principio prometí que crearíamos un servidor que se comporte como describe el OAS. elk lo hace fácil: solo debes llamar a elk.GenerateHandlers() al configurar la extensión:

ent/entc.go
[...]
func main() {
ex, err := elk.NewExtension(
elk.GenerateSpec(
[...]
),
+ elk.GenerateHandlers(),
)
[...]
}

A continuación, vuelve a ejecutar la generación de código:

go generate ./...

Observa que se ha creado un nuevo directorio llamado ent/http.

» tree ent/http
ent/http
├── create.go
├── delete.go
├── easyjson.go
├── handler.go
├── list.go
├── read.go
├── relations.go
├── request.go
├── response.go
└── update.go

0 directories, 10 files

Puedes iniciar el servidor generado con este sencillo main.go:

package main

import (
"context"
"log"
"net/http"

"<your-project>/ent"
elk "<your-project>/ent/http"

_ "github.com/mattn/go-sqlite3"
"go.uber.org/zap"
)

func main() {
// Create the ent client.
c, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer c.Close()
// Run the auto migration tool.
if err := c.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
// Start listen to incoming requests.
if err := http.ListenAndServe(":8080", elk.NewHandler(c, zap.NewExample())); err != nil {
log.Fatal(err)
}
}
go run -mod=mod main.go

Nuestro servidor de Fridge API está en funcionamiento. Con el archivo OAS generado y las herramientas Swagger, ahora puedes generar un stub de cliente en cualquier lenguaje soportado y olvidarte de escribir un cliente RESTful... ¡para siempre!

Conclusión

En esta publicación presentamos una nueva característica de elk: la generación automática de Especificaciones OpenAPI. Esta función conecta las capacidades de generación de código de Ent con el ecosistema de herramientas OpenAPI/Swagger.

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

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