Go et l'écosystème OpenAPI
Introduction
Lorsque l'on développe un serveur Backend de production avec le langage Go, l'un des premiers défis que la plupart des développeurs rencontrent est le suivant :
Comment documenter l'API ?
En cherchant un peu, on réalise qu'il est avantageux de rédiger une documentation conforme à la spécification OpenAPI, et l'on se tourne naturellement vers des bibliothèques compatibles avec OpenAPI. Cependant, même après avoir pris cette décision, un problème subsiste :
Il y a beaucoup de bibliothèques liées à OpenAPI... Laquelle devrais-je utiliser ?
Ce document est une brève présentation des bibliothèques, rédigée à l'intention des débutants en Go qui ont rencontré cette situation. Ce document a été rédigé fin 2024, et l'écosystème linguistique étant en constante évolution, il est recommandé de s'y référer tout en gardant un œil sur les dernières actualités.
Stratégies des bibliothèques face à OpenAPI
Comme vous le savez peut-être déjà, OpenAPI est une spécification destinée à définir et documenter de manière claire les REST API. Elle permet de définir les endpoints, les requêtes et les formats de réponse de l'API en format YAML ou JSON, ce qui aide non seulement les développeurs, mais aussi à automatiser la génération de code pour le frontend et le backend, réduisant ainsi les répétitions inutiles et les petites erreurs humaines.
Pour intégrer naturellement OpenAPI à un projet, les bibliothèques de l'écosystème Go adoptent principalement les trois stratégies suivantes.
1. Combinaison des commentaires Go pour générer la documentation de la spécification OpenAPI
L'une des difficultés lors du développement d'API conformes à OpenAPI est que la documentation réelle et le code qui l'implémente se trouvent souvent dans des fichiers séparés et à des emplacements différents. Il est donc fréquent d'oublier de mettre à jour la documentation après une modification du code, ou de ne pas mettre à jour le code après une modification de la documentation.
Pour prendre un exemple simple :
- Si la logique d'une API est modifiée dans un fichier tel que
./internal/server/user.go, - et que la documentation réelle existe dans
./openapi3.yaml, il est possible d'oublier par erreur de la modifier. - Si une Pull Request est soumise et que des collègues sont sollicités pour une revue sans que ce problème de modification ne soit identifié,
- les relecteurs ne verront pas non plus les modifications apportées à
./openapi3.yaml, ce qui peut entraîner une situation regrettable où la spécification de l'API reste inchangée alors que l'implémentation réelle de l'API a été modifiée.
La rédaction de la documentation API sous forme de commentaires Go peut résoudre en partie 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 le code. Il existe des outils qui génèrent automatiquement la documentation de la spécification OpenAPI à partir de ces commentaires.
Un projet représentatif est Swag. Swag analyse les commentaires du code Go pour générer une documentation au format OpenAPI 2. Son utilisation est simple : il suffit d'écrire les commentaires au-dessus des fonctions de gestionnaire, en respectant le format défini par chaque bibliothèque.
1// @Summary Création d'utilisateur
2// @Description Crée un nouvel utilisateur.
3// @Tags Utilisateurs
4// @Accept json
5// @Produce json
6// @Param user body models.User true "Informations utilisateur"
7// @Success 200 {object} models.User
8// @Failure 400 {object} models.ErrorResponse
9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11 // ...
12}
Lorsque de tels commentaires sont rédigés, un CLI nommé Swag analyse ces commentaires pour générer un document OpenAPI 2. Généralement, cette tâche est effectuée pendant le processus CI, et le document de spécification OpenAPI généré est déployé dans le Git Repository, les résultats de la compilation finale ou un système externe de gestion de la documentation API pour être utilisé en collaboration avec d'autres projets.
Avantages :
- Les commentaires étant avec le code, la probabilité que le code réel et la documentation diffèrent est réduite.
- La documentation peut être réalisée simplement et librement avec de simples commentaires, sans outils supplémentaires ni configurations complexes.
- Les commentaires n'affectant pas la logique réelle de l'API, il est facile d'ajouter des fonctionnalités temporaires qu'il serait difficile de rendre publiques dans la documentation.
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 d'API sous forme de commentaires.
- La documentation ne forçant pas le code, il n'y a aucune garantie que le document OpenAPI et la logique réelle correspondent.
2. Génération de code Go à partir d'un document de spécification 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 directement le code, cela peut forcer culturellement le développement à commencer par la conception de l'API. Étant donné que la définition de la spécification de l'API est le premier pas dans l'ordre de développement, cela a l'avantage de prévenir précocement les situations regrettables où des lacunes ne sont identifiées qu'une fois le développement terminé, entraînant des modifications de la spécification de l'API et une révision complète du code.
Les projets représentatifs adoptant cette approche sont oapi-codegen et OpenAPI Generator. L'utilisation est simple :
- Rédigez un document yaml ou json conforme à la spécification OpenAPI.
- Exécutez le CLI.
- Le code Go stub correspondant est généré.
- Il ne reste plus qu'à implémenter directement la logique détaillée pour chaque API afin que ce stub puisse être utilisé.
Voici un exemple de code généré par oapi-codegen :
1// StrictServerInterface représente tous les gestionnaires de serveur.
2type StrictServerInterface interface {
3 // ...
4 // Renvoie tous les animaux de compagnie
5 // (GET /pets)
6 FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7 // ...
8}
Le code généré par oapi-codegen, via cette interface, effectue la logique de parsing et de validation des query parameters, des headers et du body, puis appelle la méthode appropriée déclarée dans l'interface. L'utilisateur n'a plus qu'à implémenter l'implémentation de cette interface pour achever le travail nécessaire à l'implémentation de l'API.
Avantages :
- Comme la spécification est établie en premier et que le développement suit, cela favorise la progression parallèle des tâches en cas de collaboration entre plusieurs équipes.
- Le code pour les tâches répétitives et manuelles étant généré automatiquement, l'efficacité du travail est augmentée tout en restant favorable au débogage.
- Il est plus facile de garantir que la documentation et le code sont toujours cohérents.
Inconvénients :
- Si l'on est novice en matière de spécification OpenAPI, il peut y avoir une courbe d'apprentissage initiale significative.
- La forme du code de gestion de l'API étant générée automatiquement par le projet, il peut être difficile d'adapter le code si une personnalisation est nécessaire.
Commentaire de l'auteur. En octobre 2024, le code Go généré par OpenAPI Generator impose la structure globale du projet, pas seulement la logique de l'API, ce qui rend la structure du projet rigide. Il génère un code qui est inapproprié pour ajouter diverses fonctionnalités nécessaires dans un environnement de production réel. Pour ceux qui adoptent cette approche, il est fortement recommandé d'utiliser oapi-codegen. L'auteur utilise oapi-codegen + echo + StrictServerInterface.
3. Génération de la documentation de 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 inévitable est la rupture de l'uniformité des API individuelles. À titre d'exemple intuitif, si les spécifications de plus de 100 endpoints d'API sont déclarées dans un seul fichier OpenAPI yaml, ce fichier deviendra un monstre de plus de 10 000 lignes. La déclaration de nouveaux endpoints d'API entraînera inévitablement la déclaration en double du même modèle, l'omission de certains champs, ou la création de noms de chemins non conformes aux conventions, ce qui brisera l'uniformité globale de l'API.
Pour résoudre ce problème, on pourrait désigner un propriétaire distinct pour gérer l'OpenAPI yaml, ou développer un Linter pour détecter automatiquement les incohérences pendant le processus de CI. Cependant, il est également possible de définir un Domain-specific language (DSL) en Go pour forcer toutes les API à avoir une uniformité cohérente.
Kubernetes est un projet représentatif utilisant cette technique (il a été construit en interne sans bibliothèque externe), et on peut l'utiliser avec des projets tels que go-restful et 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 écrivant du code Go compilable comme ci-dessus, on obtient l'avantage de réaliser simultanément l'implémentation de l'API POST /users et la définition de sa documentation.
Avantages :
- Tout étant généré à partir du code, il est facile de maintenir la cohérence de l'API sur l'ensemble du projet.
- En utilisant le système de typage fort de Go, on peut obtenir une spécification plus précise et moins sujette à controverse que si l'on utilisait toutes les fonctionnalités d'OpenAPI3.
Inconvénients :
- Il faut apprendre le DSL défini par chaque framework, et il peut être difficile de l'appliquer au code existant.
- Comme il faut suivre les règles proposées par le framework, la liberté et la flexibilité peuvent être réduites.
En conclusion
Chaque méthode présente des avantages et des inconvénients, et il est crucial de choisir celle qui convient le mieux aux exigences du projet et aux préférences de l'équipe. Le plus important n'est pas de savoir quelle méthode est la meilleure, mais de juger quelle solution est la plus appropriée à votre situation actuelle et d'augmenter la productivité du développement pour profiter d'une fin de journée rapide et d'un équilibre vie professionnelle-vie privée satisfaisant.
Bien que cet article ait été rédigé en octobre 2024, l'écosystème Go et OpenAPI évolue constamment. Il est donc conseillé de suivre en permanence l'actualité des bibliothèques et des projets, ainsi que l'évolution de leurs avantages et inconvénients, en tenant compte de l'écart de temps depuis la lecture de cet article.
Je vous souhaite une excellente vie en Go ! 😘