Go y el ecosistema OpenAPI
Introducción
Al desarrollar un servidor backend de producción en Go, uno de los primeros desafíos que casi todos los desarrolladores enfrentan es el siguiente:
¿Cómo documentar las APIs...?
Si se investiga un poco sobre esto, se llega a la conclusión de que es beneficioso escribir la documentación conforme a la especificación de OpenAPI, lo que lleva naturalmente a la búsqueda de 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 debo usar...?
Este documento es una breve introducción a las bibliotecas, escrita para los principiantes de Go que se encuentran en esta situación. Se redactó a finales de 2024 y, dado que el ecosistema del lenguaje está en constante cambio, se recomienda consultarlo mientras se revisan las últimas noticias.
Estrategias de las Bibliotecas para Abordar OpenAPI
Como ya sabrá, OpenAPI es una especificación para definir y documentar claramente las REST API. Define los puntos finales de la API, las solicitudes y los formatos de respuesta en formato YAML o JSON, lo que ayuda a los desarrolladores, así como al frontend y al backend, a automatizar la generación de código, reduciendo repeticiones innecesarias y pequeños errores humanos.
Para integrar OpenAPI de forma natural en los proyectos, las bibliotecas del ecosistema Go adoptan principalmente las siguientes tres estrategias.
1. Combinación de Comentarios de Go en Documentos de Especificación de OpenAPI
Una de las dificultades a la hora de desarrollar una API según OpenAPI es que el documento real y el código que implementa este documento existen en archivos separados en ubicaciones completamente diferentes, lo que hace que sea sorprendentemente frecuente que el código se actualice sin actualizar el documento, o que el documento se actualice sin actualizar el código.
Por ejemplo:
- Si se modifica la lógica de una API en el archivo
./internal/server/user.go
, - El documento real existe en
./openapi3.yaml
, y se puede olvidar por error esta modificación. - Si no se reconoce este problema y se envía una solicitud de extracción para que la revisen los compañeros,
- Los revisores tampoco verán los cambios en
./openapi3.yaml
, por lo que puede producirse el desafortunado caso de que la especificación de la API se mantenga igual, pero la implementación real de la API haya cambiado.
Al escribir la documentación de la API en forma de comentarios de Go, este problema se puede resolver en cierta medida. Dado que el código y el documento están en el mismo lugar, los comentarios se pueden actualizar junto con la modificación del código. Existen herramientas que generan automáticamente documentos de especificación de OpenAPI basados en estos comentarios.
Un proyecto representativo es Swag. Swag analiza los comentarios del código Go y genera documentos en formato OpenAPI 2. El uso es sencillo. Simplemente escriba los comentarios en el formato definido por cada biblioteca sobre la función del controlador.
1// @Summary Creación de 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 de 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 comentarios de esta forma, el CLI de Swag analiza estos comentarios y genera un documento OpenAPI 2. Generalmente, este proceso se realiza durante la integración continua (CI), y el documento de especificación de OpenAPI generado se implementa en el repositorio de Git, el resultado final de la compilación, o un sistema externo de gestión de documentos de API independiente, para su uso en la colaboración con otros proyectos.
Ventajas:
- Dado que los comentarios están junto con el código, se reduce la probabilidad de que la forma del código real y el documento sea diferente.
- Se puede documentar de forma fácil y libre solo con comentarios, sin necesidad de herramientas adicionales o configuraciones complejas.
- Dado que los comentarios no afectan la lógica real de la API, es bueno agregar funciones temporales que no se desea revelar en el documento.
Desventajas:
- A medida que aumenta el número de líneas de comentarios, la legibilidad de un único archivo de código puede verse reducida.
- Puede ser difícil expresar todas las especificaciones de la API en forma de comentarios.
- Dado que el documento no obliga al código, no hay garantía de que el documento de OpenAPI coincida con la lógica real.
2. Generación de Código Go a partir de Documentos de Especificación de OpenAPI
También existe una forma de colocar la única fuente de verdad (SSOT) en el lado del documento, no en el código Go. Es decir, primero se define la especificación de OpenAPI y, basándose en el contenido definido, se genera el código Go. Dado que la especificación de la API genera el código, se puede forzar culturalmente la definición de la API primero y, dado que la definición de la especificación de la API es lo primero que se hace en el orden del desarrollo, se puede evitar el infortunio de que la modificación de toda la especificación de la API y el código se realice después de que se complete el desarrollo y se reconozcan las partes que faltan.
Los proyectos representativos que adoptan este enfoque son oapi-codegen y OpenAPI Generator. El uso es sencillo:
- Se escribe un documento yaml o json de acuerdo con la especificación de OpenAPI y
- Se ejecuta el CLI,
- Se genera el código stub de Go correspondiente.
- Ahora, solo tiene que implementar directamente la lógica detallada para cada API para que este stub pueda usarse.
El siguiente es 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}
A través de esta interfaz, el código generado por oapi-codegen realiza la lógica de parámetros de consulta, encabezado, análisis de cuerpo y validación, y llama al método apropiado declarado en la interfaz. El usuario solo tiene que implementar la implementación para esta interfaz para completar el trabajo necesario para la implementación de la API.
Ventajas:
- Dado que la especificación sale primero y luego se procede con el desarrollo, es ventajoso para realizar el trabajo en paralelo cuando varios equipos están colaborando.
- Dado que el código para las partes que se realizaban con trabajo repetitivo se genera automáticamente, la eficiencia del trabajo aumenta y también sigue siendo beneficioso para la depuración.
- Es fácil garantizar que la forma del documento y el código siempre coincidan.
Desventajas:
- Si no se tiene conocimiento de la especificación de OpenAPI, existe una cierta curva de aprendizaje inicial.
- Dado que la forma del código que maneja las APIs se genera automáticamente por el proyecto, puede ser difícil de manejar si se necesita una personalización.
Comentario del autor. A partir de octubre de 2024, el código Go generado por OpenAPI Generator fuerza la forma de todo el proyecto, no solo la lógica de la API, y la estructura del proyecto es rígida, por lo que genera un código de forma inadecuada para agregar varias funciones necesarias en un entorno de producción real. Se recomienda encarecidamente a aquellos que adopten este método que utilicen oapi-codegen. El autor está usando oapi-codege + echo + StrictServerInterface.
3. Generación de Documentos de Especificación de OpenAPI con Código Go
Un problema que surge inevitablemente cuando decenas o cientos de personas están desarrollando el mismo servidor es que la uniformidad puede romperse por cada API individual. Un ejemplo intuitivo es que, si se declaran especificaciones para más de 100 puntos finales de API en un solo archivo yaml de OpenAPI, ese archivo será un monstruo de más de 10,000 líneas y, al declarar un nuevo punto final de API, inevitablemente se declarará el mismo modelo de forma duplicada o se omitirán algunos campos, o se creará un nombre de ruta que no se ajuste a la convención.
Para resolver este problema, se puede asignar un propietario para gestionar el yaml de OpenAPI, o se puede desarrollar un Linter para que se detecte automáticamente durante el proceso de CI, pero se puede definir un lenguaje específico de dominio (DSL) en Go para forzar que todas las API tengan una uniformidad consistente.
Kubernetes es un proyecto representativo que utiliza esta técnica (construido de forma independiente sin una biblioteca independiente), y también se puede probar utilizando proyectos como go-restful y goa. El siguiente es un ejemplo del 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 un código Go compilable como el anterior, se puede obtener la ventaja de que la implementación y la definición del documento para la API POST /users
se completan simultáneamente.
Ventajas:
- Dado que todo proviene del código, es fácil mantener la consistencia de la API en todo el proyecto.
- Al utilizar el sistema de tipos fuertes de Go, se puede obtener una especificación más precisa y sin controversias que al utilizar todas las funciones de OpenAPI3.
Desventajas:
- Es necesario aprender el DSL definido en cada framework, y puede ser difícil de aplicar al código existente.
- Dado que las reglas propuestas por el framework deben seguirse a la fuerza, la libertad y la flexibilidad pueden verse reducidas.
Concluyendo
Cada método tiene ventajas y desventajas, y es importante elegir el método adecuado según los requisitos del proyecto y las preferencias del equipo. Lo más importante siempre es no qué método es mejor, sino evaluar cuál es la solución más adecuada para la situación actual, y aumentar la productividad del desarrollo para disfrutar de una salida anticipada del trabajo y un equilibrio satisfactorio entre la vida laboral y personal.
Aunque este artículo se escribió a partir de octubre de 2024, el ecosistema de Go y OpenAPI está en constante evolución, por lo que se recomienda que se revise continuamente el estado actual de cada biblioteca y proyecto, así como sus ventajas y desventajas cambiantes, teniendo en cuenta el intervalo entre el momento en que se lee este artículo y el presente.
¡Que tenga una feliz vida en Go! 😘