L'écosystème Go et OpenAPI

Introduction
Lorsque l'on développe un serveur backend de production en Go, l'un des premiers défis que rencontrent presque tous les développeurs est le suivant.
Comment documenter l'API... ?
En cherchant un peu, on comprend qu'il est avantageux de rédiger une documentation conforme à la spécification OpenAPI, et l'on cherche naturellement une bibliothèque compatible avec OpenAPI. Mais même après avoir pris cette décision, un autre problème se présente.
Il existe beaucoup de bibliothèques liées à OpenAPI... laquelle faut-il utiliser... ?
Ce document est une brève présentation des bibliothèques, rédigée à l'intention des débutants en Go qui vivent cette situation. Il a été rédigé à la fin de l'année 2024, et comme l'écosystème du langage évolue constamment, nous recommandons de le consulter en vérifiant régulièrement les dernières actualités.
Les stratégies des bibliothèques face à OpenAPI
Vous le savez sans doute déjà, OpenAPI est une spécification destinée à définir et documenter clairement les API REST. En définissant les endpoints, les formats de requête et de réponse, etc., au format YAML ou JSON, elle contribue largement à automatiser la génération de code pour les développeurs, côté frontend comme côté backend, ce qui réduit les répétitions inutiles et diminue les petites erreurs humaines.
Afin d'intégrer naturellement cette spécification OpenAPI dans un projet, les bibliothèques de l'écosystème Go adoptent globalement les trois stratégies suivantes.
1. Composer la spécification OpenAPI à partir de commentaires Go
L'une des difficultés du développement d'API conformes à OpenAPI tient au fait que la documentation réelle et le code qui l'implémente se trouvent dans des fichiers distincts, à des emplacements totalement différents. Il arrive donc assez souvent que l'on mette à jour le code sans mettre à jour la documentation, ou l'inverse.
Prenons un exemple simple.
- Vous modifiez la logique de l'API dans le fichier
./internal/server/user.go. - La documentation réelle se trouve dans
./openapi3.yaml, et vous pouvez oublier par inadvertance de la modifier. - Sans avoir remarqué ce problème, vous soumettez une Pull Request et la faites relire par vos collègues.
- Comme les relecteurs ne voient pas non plus les modifications apportées à
./openapi3.yaml, il peut arriver que la spécification de l'API reste inchangée alors que l'implémentation réelle de l'API a été modifiée, ce qui constitue un incident fâcheux.
Rédiger la documentation de l'API sous forme de commentaires Go permet de résoudre dans une certaine mesure ce problème. Le code et la documentation étant regroupés au même endroit, il est possible de mettre à jour les commentaires en même temps que l'on modifie le code. Il existe des outils qui génèrent automatiquement une spécification OpenAPI à partir de ces commentaires.
Un projet représentatif est Swag. Swag analyse les commentaires du code Go et génère une documentation au format OpenAPI 2. Son utilisation est simple : il suffit de rédiger des commentaires au-dessus des fonctions de gestion (handlers), selon le format défini par la bibliothèque.
1// @Summary 유저 생성
2// @Description 새로운 유저를 생성합니다.
3// @Tags Users
4// @Accept json
5// @Produce json
6// @Param user body models.User true "유저 정보"
7// @Success 200 {object} models.User
8// @Failure 400 {object} models.ErrorResponse
9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11 // ...
12}
Une fois ces commentaires écrits, l'outil CLI Swag les analyse et génère la documentation OpenAPI 2. Ce processus est généralement exécuté dans le cadre du CI, et la spécification OpenAPI générée est déployée dans le dépôt Git, dans le résultat final de la compilation, ou dans un système externe de gestion de documentation d'API, afin d'être utilisée lors de la collaboration avec d'autres projets.
Avantages :
- Les commentaires se trouvant avec le code, le risque de divergence entre le code réel et la documentation diminue.
- Il est possible de documenter de manière simple et souple, uniquement par des commentaires, sans outil supplémentaire ni configuration complexe.
- Les commentaires n'affectant pas la logique réelle de l'API, il est facile d'ajouter des fonctionnalités temporaires dont la publication dans la documentation serait gênante.
Inconvénients :
- L'augmentation du nombre de lignes de commentaires peut réduire la lisibilité d'un fichier de code unique.
- Il peut être difficile d'exprimer toutes les spécifications de l'API sous forme de commentaires.
- La documentation n'imposant pas le code, il n'est pas garanti que la documentation OpenAPI corresponde à la logique réelle.
2. Générer du code Go à partir de la documentation OpenAPI
Il existe également une approche consistant à placer la Single Source of Truth (SSOT) non pas dans le code Go, mais dans la documentation. Il s'agit de définir d'abord la spécification OpenAPI, puis de générer le code Go à partir de son contenu. Comme la spécification de l'API génère elle-même le code, elle permet d'imposer culturellement de concevoir l'API en premier. Et comme la définition de la spécification est la toute première étape du développement, cette méthode présente l'avantage d'éviter très tôt le scénario fâcheux où, une fois le développement terminé, on prend conscience d'oublis et l'on doit modifier l'ensemble du code en même temps que la spécification.
Les projets représentatifs qui adoptent cette approche sont oapi-codegen et OpenAPI Generator. Leur utilisation est simple.
- Rédiger un document YAML ou JSON conforme à la spécification OpenAPI,
- Exécuter la CLI,
- Le code stub Go correspondant est généré.
- Il ne reste plus qu'à implémenter la logique détaillée de chaque API afin que ce stub puisse être utilisé.
Voici un exemple de code généré par oapi-codegen.
1// StrictServerInterface represents all server handlers.
2type StrictServerInterface interface {
3 // ...
4 // Returns all pets
5 // (GET /pets)
6 FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7 // ...
8}
Le code généré par oapi-codegen à partir de cette interface effectue le traitement des query parameters, des headers, du parsing du body ainsi que la validation, puis appelle la méthode appropriée déclarée dans l'interface. L'utilisateur n'a qu'à implémenter cette interface pour que le travail nécessaire à l'implémentation de l'API soit achevé.
Avantages :
- Comme la spécification est établie en premier et le développement suit, il est avantageux de paralléliser les tâches lorsque plusieurs équipes collaborent.
- Le code correspondant aux parties répétitives et fastidieuses est généré automatiquement, ce qui améliore l'efficacité du travail tout en facilitant le débogage.
- Il est facile de garantir que la documentation et la structure du code restent toujours cohérentes.
Inconvénients :
- Une courbe d'apprentissage initiale existe si l'on ne connaît pas bien la spécification OpenAPI.
- La structure du code gérant l'API étant générée automatiquement selon le projet, il peut être difficile de répondre aux besoins de personnalisation.
Commentaire de l'auteur. En octobre 2024, le code Go généré par OpenAPI Generator impose non seulement la logique de l'API, mais aussi l'ensemble de la structure du projet ; ce code est rigide et peu adapté à l'ajout des diverses fonctionnalités nécessaires dans un environnement de production. Pour ceux qui adoptent cette approche, nous recommandons vivement d'utiliser oapi-codegen. L'auteur utilise oapi-codegen + echo + StrictServerInterface.
3. Générer la spécification OpenAPI à partir du code Go
Lorsque des dizaines, voire des centaines de personnes développent sur le même serveur, un problème qui survient inévitablement est que la cohérence entre les différentes API peut se rompre. À titre d'exemple intuitif, si la spécification de plus de 100 endpoints est déclarée dans un seul fichier OpenAPI YAML, ce fichier deviendra un monstre de plus de 10 000 lignes, et l'ajout de nouveaux endpoints entraînera inévitablement la déclaration en double d'un même modèle, l'omission de certains champs, ou la création de noms de chemins non conformes aux conventions, de sorte que la cohérence globale de l'API commencera à se dégrader.
Pour résoudre ce problème, on pourrait désigner un responsable chargé de la gestion du fichier OpenAPI YAML, ou développer un linter détectant automatiquement ces erreurs pendant le CI. Mais il est également possible d'imposer une cohérence uniforme à toutes les API en définissant un Domain-specific language (DSL) en Go.
Un projet représentatif utilisant cette technique est Kubernetes (qui la met en œuvre de manière autonome, sans bibliothèque externe). On peut également essayer des projets comme go-restful ou goa. Voici un exemple d'utilisation 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})
En rédigeant du code Go compilable de cette manière, on obtient l'avantage que l'implémentation de l'API POST /users et sa définition documentaire sont complétées simultanément.
Avantages :
- Tout découlant du code, il est facile de maintenir la cohérence des API à l'échelle du projet.
- En tirant parti du système de types forts de Go, on obtient une spécification plus précise et moins sujette à controverse que si l'on utilisait toutes les fonctionnalités d'OpenAPI 3.
Inconvénients :
- Il faut apprendre le DSL défini par chaque framework, et son application au code existant peut être difficile.
- Devant suivre les règles proposées par le framework, la liberté et la souplesse peuvent être réduites.
Conclusion
Chaque méthode présente ses avantages et ses inconvénients, et il est important de choisir l'approche la plus adaptée aux exigences du projet et aux préférences de l'équipe. Ce qui compte le plus n'est pas de savoir quelle méthode est la meilleure, mais d'évaluer quelle solution correspond le mieux à la situation dans laquelle on se trouve, et d'accroître la productivité du développement afin de profiter d'un départ rapide du bureau et d'un équilibre vie professionnelle-vie personnelle satisfaisant.
Bien que cet article ait été rédigé en octobre 2024, les écosystèmes de Go et d'OpenAPI évoluent continuellement. Compte tenu de l'écart entre le moment de la lecture et celui de la rédaction, nous vous invitons à continuer de suivre les actualités ainsi que les évolutions des avantages et inconvénients de chaque bibliothèque et projet.
Bonne vie avec Go ~ 😘