GoSuda

Go y el ecosistema OpenAPI

By iwanhae
views ...

Introducción

Al desarrollar un servidor Backend de Producción en el lenguaje Go, uno de los desafíos más comunes que la mayoría de los desarrolladores encuentran al principio es el siguiente:

Documentación de API, ¿cómo se hace...?

Si se investiga un poco sobre el tema, se llega a la conclusión de que es beneficioso redactar la documentación de acuerdo con la especificación OpenAPI, y naturalmente se busca una biblioteca que se integre con OpenAPI. Sin embargo, incluso después de tomar esta decisión, surge el siguiente problema:

Hay muchas bibliotecas relacionadas con OpenAPI... ¿cuál debería usar...?

Este documento es una breve introducción a las bibliotecas, escrita para los principiantes en Go que están experimentando esta situación. El documento fue redactado a finales de 2024, y dado que el ecosistema del lenguaje está en constante cambio, se recomienda consultarlo y mantenerse al tanto de las últimas novedades.

Estrategias de las bibliotecas para abordar OpenAPI

Como ya sabrá, OpenAPI es una especificación para definir y documentar REST APIs de manera clara. Define los endpoints, las solicitudes y los formatos de respuesta de la API en formato YAML o JSON, lo que no solo ayuda a los desarrolladores, sino que también automatiza la generación de código para el frontend y el backend, reduciendo la repetición innecesaria y minimizando los pequeños errores humanos.

Para integrar OpenAPI de manera natural en los proyectos, las bibliotecas del ecosistema Go adoptan principalmente las siguientes tres estrategias.

1. Combinar comentarios de Go en documentos de especificación OpenAPI

Uno de los aspectos complicados al desarrollar APIs según OpenAPI es que la documentación real y el código que la implementa existen en archivos separados y en ubicaciones completamente diferentes, lo que a menudo lleva a situaciones en las que se actualiza el código pero no la documentación, o viceversa.

Por ejemplo, de manera sencilla:

  1. Se modifica la lógica de la API en un archivo llamado ./internal/server/user.go.
  2. La documentación real se encuentra en ./openapi3.yaml, y se puede olvidar accidentalmente actualizarla.
  3. Si se envía un Pull Request sin darse cuenta de este problema en los cambios y se pide revisión a los compañeros,
  4. Los revisores tampoco verán los cambios en ./openapi3.yaml, lo que podría llevar a la desafortunada situación de que la especificación de la API permanezca igual, pero la implementación real de la API haya cambiado.

Escribir la documentación de la API en forma de comentarios de Go puede solucionar este problema hasta cierto punto. Dado que el código y la documentación están en un solo lugar, se pueden actualizar los comentarios junto con el código. Existen herramientas que generan automáticamente documentos de especificación OpenAPI basados en estos comentarios.

Un proyecto representativo es Swag. Swag analiza los comentarios del código Go para generar documentos en formato OpenAPI 2. Su uso es sencillo. Solo hay que escribir comentarios encima de la función de controlador, siguiendo el formato definido por cada biblioteca.

 1// @Summary Crear usuario
 2// @Description Crea un nuevo usuario.
 3// @Tags Users
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "Información del usuario"
 7// @Success 200 {object} models.User
 8// @Failure 400 {object} models.ErrorResponse
 9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11    // ...
12}

Al escribir los comentarios de esta manera, la CLI de Swag los analiza para generar el documento OpenAPI 2. Generalmente, esta tarea se realiza durante el proceso de CI, y el documento de especificación OpenAPI generado se despliega en el Repositorio Git, en el resultado final de la construcción, o en un sistema externo de gestión de documentación de API para su uso en la colaboración con otros proyectos.

Ventajas:

  • Dado que los comentarios están junto con el código, la posibilidad de que el código real y la documentación difieran se reduce.
  • Permite una documentación sencilla y flexible solo con comentarios, sin herramientas adicionales ni configuraciones complejas.
  • Dado que los comentarios no afectan la lógica real de la API, es excelente para agregar funcionalidades temporales que serían difíciles de 공개ar en la documentación.

Desventajas:

  • La legibilidad de un archivo de código único puede disminuir a medida que aumenta el número de líneas de comentarios.
  • Puede ser difícil expresar todas las especificaciones de la API en forma de comentarios.
  • Dado que la documentación no impone el código, no se puede garantizar que la documentación de OpenAPI y la lógica real coincidan.

2. Generar código Go a partir de un documento de especificación OpenAPI

También existe un enfoque en el que la Fuente Única de Verdad (SSOT) no es el código Go, sino la documentación. Se trata de definir primero la especificación OpenAPI y luego generar el código Go basándose en el contenido definido. Dado que la especificación de la API genera el código, culturalmente se puede forzar el diseño de la API primero, y dado que la definición de la especificación de la API es el primer paso en el orden de desarrollo, tiene la ventaja de evitar la desafortunada situación de que se detecten omisiones solo después de que el desarrollo esté completo y se requiera una modificación de la especificación de la API junto con una revisión de todo el código.

Entre los proyectos representativos que adoptan este enfoque se encuentran oapi-codegen y OpenAPI Generator. Su uso es sencillo:

  1. Se redacta el documento yaml o json según la especificación OpenAPI.
  2. Se ejecuta la CLI.
  3. Se genera el código stub de Go correspondiente.
  4. Ahora, solo es necesario implementar directamente la lógica detallada para cada API para que este stub pueda utilizarla.

A continuación, se muestra un ejemplo del código generado por oapi-codegen:

1// StrictServerInterface representa todos los manejadores del servidor.
2type StrictServerInterface interface {
3	// ...
4	// Devuelve todas las mascotas
5	// (GET /pets)
6	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7	// ...
8}

El código generado por oapi-codegen a partir de la interfaz anterior realiza la lógica de análisis y validación de query parameters, headers y body, y llama al método apropiado declarado en la interfaz. El usuario solo necesita implementar la implementación de esta interfaz para completar la tarea de implementación de la API.

Ventajas:

  • Dado que la especificación se define primero y luego se procede con el desarrollo, es ventajoso para la colaboración paralela cuando varios equipos trabajan juntos.
  • El código para las partes que se realizaban de forma repetitiva y manual se genera automáticamente, lo que aumenta la eficiencia del trabajo y sigue siendo ventajoso para la depuración.
  • Es fácil garantizar que la documentación y la estructura del código siempre coincidan.

Desventajas:

  • Si no se tiene conocimiento de la especificación OpenAPI, la curva de aprendizaje inicial puede ser considerable.
  • Dado que la estructura del código que maneja la API se genera automáticamente por el proyecto, puede ser difícil adaptarse si se requiere personalización.

Comentario del autor. A octubre de 2024, el código Go generado por OpenAPI Generator no solo impone la lógica de la API, sino también la estructura general del proyecto, lo que resulta en un código rígido y, por lo tanto, inadecuado para agregar diversas funcionalidades necesarias en un entorno de Producción real. Para aquellos que adopten este enfoque, se recomienda encarecidamente utilizar oapi-codegen. El autor utiliza oapi-codegen + echo + StrictServerInterface.

3. Generar un documento de especificación OpenAPI a partir de código Go

Cuando cientos de personas desarrollan el mismo servidor, es inevitable que surjan problemas de inconsistencia entre las APIs individuales. Un ejemplo intuitivo es que si se declaran más de 100 Endpoints de API en un único archivo YAML de OpenAPI, dicho archivo se convertirá en un monstruo de más de 10,000 líneas, y al declarar nuevos Endpoints de API, inevitablemente se declarará el mismo modelo de forma duplicada, se omitirán algunos campos o se crearán nombres de Path que no se ajustan a la convención, lo que romperá la coherencia general de la API.

Para resolver este problema, se puede asignar un propietario separado para gestionar el archivo YAML de OpenAPI, o desarrollar un Linter para que se detecten automáticamente las inconsistencias durante el proceso de CI. Sin embargo, también se puede definir un Domain-specific language (DSL) en Go para forzar que todas las APIs tengan una consistencia uniforme.

Proyectos representativos que utilizan esta técnica son Kubernetes (construido internamente sin una biblioteca separada), y también se puede usar con proyectos como go-restful y goa. A continuación, se muestra un ejemplo de uso de goa:

 1var _ = Service("user", func() {
 2    Method("create", func() {
 3        Payload(UserPayload)
 4        Result(User)
 5        HTTP(func() {
 6            POST("/users")
 7            Response(StatusOK)
 8        })
 9    })
10})

Al escribir código Go compilable como el anterior, se obtiene la ventaja de que la implementación de la API POST /users y la definición de la documentación se completan simultáneamente.

Ventajas:

  • Dado que todo se deriva del código, es fácil mantener la coherencia de la API en todo el proyecto.
  • Al utilizar el sistema de tipado fuerte de Go, se puede obtener una especificación más precisa e indiscutible que al usar todas las funcionalidades de OpenAPI3.

Desventajas:

  • Es necesario aprender el DSL definido por cada framework, y puede ser difícil aplicarlo al código existente.
  • Dado que se deben seguir obligatoriamente las reglas propuestas por el framework, la libertad y flexibilidad pueden verse reducidas.

Conclusión

Cada método tiene sus ventajas y desventajas, y es crucial elegir el más adecuado según los requisitos del proyecto y las preferencias del equipo. Lo más importante no es qué método es mejor, sino realizar un juicio de valor sobre cuál es la solución más apropiada para la situación actual, aumentar la productividad del desarrollo para salir temprano del trabajo y disfrutar de un satisfactorio equilibrio entre vida laboral y personal.

Aunque este artículo fue escrito en octubre de 2024, el ecosistema de Go y OpenAPI está en constante evolución. Por lo tanto, se recomienda seguir de cerca el estado actual y los cambios en las ventajas y desventajas de cada biblioteca y proyecto, teniendo en cuenta el tiempo transcurrido desde la lectura de este texto.

¡Que disfruten de una feliz vida con Go! 😘