Exploiter OpenAPI (1ère partie) — Créer et maintenir une documentation API

Frédéric Jaume
Technologie @ OpenClassrooms
8 min readApr 15, 2024

Notre parcours

Tout d’abord, un peu de contexte. La plateforme OpenClassrooms repose sur une stack technique React côté frontend et sur un projet PHP Symfony côté backend. Le frontend utilise l’API exposée par le backend. Le projet backend effectue également d’autres tâches, mais son utilisation principale consiste à exposer l’API.

Nous utilisons l’API en interne, mais nous avons également des clients externes qui utilisent notre API pour intégrer notre catalogue dans leur plateforme, donc la nécessité d’une documentation claire est vitale.

De plus, nous sommes adeptes de l’approche “le code se documente lui-même” et nous appliquons des pratiques de clean code en donnant une grande importance à la lisibilité du code et à sa réutilisabilité.

Au bout d’un moment dans la vie de ce projet initié en 2012, il est devenu nécessaire d’avoir une sorte de documentation d’API. Une documentation Swagger a donc été créée. Elle était maintenue manuellement et régulièrement désynchronisée. Certains développeurs ignoraient même son existence, il n’y avait aucun processus très formel autour de ce fichier.

En 2020, nous avons décidé d’opter plutôt pour la spécification OpenAPI et avons prévu de construire un schéma pour notre API, qui comptait déjà plus d’une centaine de routes. La création manuelle du schéma était hors de question. L’un de nos ingénieurs s’est chargé de coder une commande CLI (en PHP) pour extraire un schéma OpenAPI à partir de la base de code. Ca n’était pas trivial, mais une fois cela fait, ça a ouvert de nombreuses portes.

Construire un monolithe

Photo by Karthik Sreenivas on Unsplash

Nous nous sommes retrouvés avec un fichier OpenAPI auto-généré de 7000 lignes. Il y avait quelques problèmes à régler en termes de lisibilité, mais note schéma était assez complet.

Nous avions maintenant une documentation que nos ingénieurs frontend pouvaient utiliser comme référence, et elle pouvait également être facilement partagée avec les ingénieurs qualité, les parties prenantes et nos clients.

Quelques années ont passé, l’entreprise a grandi, et nous avions désormais plus de 40 ingénieurs, plusieurs équipes, de nouvelles fonctionnalités sur la plateforme…

Fin 2022, notre schéma OpenAPI contenait environ 240 définitions de routes (et chaque route comportait souvent plusieurs opérations, comme la plupart des routes CRUD). Le fichier YAML assez monstrueux de notre documentation OpenAPI comprenait alors 410 schémas et 32 000 lignes, pour un poids total de 928 Ko. Il est devenu à nouveau difficile à maintenir, pénible d’ajouter des routes, d’effectuer des code review lisibles, de refactoriser et même de simplement l’ouvrir dans certains éditeurs qui n’arrivaient pas à le parser rapidement. Mais nous avons continué.

Aperçu de notre documentation OpenAPI initiale et ses 32k lignes
Notre fichier de définition OpenAPI initial, avec plus de 32000 lines de YML, et essentiellement des schemas embarqués

Exploser le monolithe

Photo by Nathalia Segato on Unsplash

Notre schéma OpenAPI était en quelque sorte devenu le cerveau de notre API (teaser: je détaillerai cela dans le prochain article). Cependant, il était aussi devenu très difficile à maintenir. Des erreurs et des incohérences ont commencé à apparaître dans la documentation. Les ingénieurs copiaient-collaient des composants, ce qui n’arrangeait rien.

Nous avions au début une connaissance très basique de la spécification OpenAPI. Mais dès qu’une équipe dédiée à pu se concentrer sur les sujets liés à OpenAPI, en apprenant de plus en plus sur les spécifications, nous avons enfin pu voir tout ce qui n’allait pas dans notre schéma initial. Nous n’avions presque pas utilisé de modèles partagés, nous n’avions même pas utilisé les composants requestBodies ou responses. Tout était schemas et paths. Le mécanisme de référence externe était à peine utilisé.

Le système de référence d’OpenAPI
Utiliser des références vers des fichiers dans la définition OpenAPI racine

Nous avions vu quelques schémas OpenAPI vraiment bien faits, et nous voulions arriver à quelque chose de similaire : avoir un fichier racine principal, et utiliser le mécanisme de référence vers des fichiers pour diviser notre gros fichier en centaines de définitions plus petites et plus faciles à gérer. Cela ouvrirait la voie à une meilleure réutilisabilité et faciliterait également beaucoup les revues de code. Des outils étaient disponibles pour ré-assembler vers un monolithe si nécessaire. Et nous n’étions certainement pas seuls avec des API aussi volumineuses, des API comme celles de Digital Ocean ou Box.com furent une bonne source d’inspiration.

Maintenant, comment casser ce monolithe ? Le refactoring manuel était hors de question. Bien sûr, nous l’avions déjà fait, il suffisait de coder un outil pour automatiser cela !

Les choses se corsent

Photo by Nik Shuliahin 💛💙 on Unsplash

Le proof-of-concept initial s’est concrétisé rapidement. Le découpage en composants basés sur le fichier existant était relativement facile. Mais notre objectif était non seulement de découper, mais aussi d’utiliser au mieux la spécification OpenAPI. Cela signifiait convertir tous les schémas proprement en requêtes et en réponses en fonction de certaines heuristiques, traiter les routes pour extraire les paramètres d’URL, d’en-tête et de requête, etc. Après quelques semaines, nous nous rapprochions rapidement de notre objectif, mais notre commande de découpage avait elle-même dépassé le cadre initial et était devenue beaucoup plus complexe. Nous avons dû la refactoriser. Le nettoyage du projet nous a permis d’ajouter de nouvelles fonctionnalités beaucoup plus rapidement à notre splitter OpenAPI, notamment la détection de composants en double, que nous avons pu fusionner. Nous pouvions également détecter les modèles inutilisés et les supprimer.

Un aperçu de la nouvelle structure
Notre nouvelle définition OpenAPI et ses différents dossiers

Dès le début, nous avons voulu nous assurer que la documentation que nous avions générée était une OpenAPI 3.0 valide, nous avons donc cherché un linter. La bibliothèque PHP que nous utilisons (cebe/php-openapi) comprend une commande de validation CLI, nous avons donc essayé celle-là en premier. Ca a fonctionné, dans une certaine mesure, mais c’était très limité et nous avons rapidement découvert que la commande ne détectait pas certaines erreurs et indiquait des erreurs qui étaient en fait des déclarations OpenAPI 3.0 valides! Nous avons probablement testé tous les linters OpenAPI… et chacun d’entre eux posait un problème. Ils ne signalaient même pas les mêmes erreurs dans le schéma.

Comme nous utilisions déjà Stoplight, nous avons opté pour Spectral en tant que linter, il n’était pas non plus parfait, mais nous pouvions commencer à définir notre propre ensemble de règles personnalisées et espérer que les problèmes seraient résolus dans les versions futures. Et puis… Nous avons eu une mauvaise surprise. Certaines définitions OpenAPI valides généraient des erreurs avec Spectral, ce qui était probablement lié à notre utilisation intensive de références vers des fichiers. En parcourant des dizaines de fils de discussion sur GitHub, nous avons découvert que nous rencontrions tous essentiellement les mêmes problèmes et trouvions les mêmes moyens de contourner les limitations, dans notre cas, c’était simple: nous regroupons les fichiers en un monolithe avant de les passer à des outils tiers et cela fonctionne mieux.

Au fur et à mesure que les choses se sont stabilisées, nous avons dû aborder une autre tâche importante, que nous avions grandement sous-estimée: les conventions de nommage (et Phil Karlton nous avait prévenus):

Il y a seulement deux choses très difficiles en Informatique: l’invalidation de cache et nommer les choses.

— Phil Karlton

Comme nous générions une structure de répertoire complètement nouvelle, avec de nouveaux fichiers, pour des composants qui n’existaient pas auparavant, comment bien nommer tous ces éléments pour que cela soit propre et clair pour nos ingénieurs ?

Stabilisation

Photo by Katerina May on Unsplash

Il nous a fallu un certain temps, mais nous avons finalement établi des règles de nommage, qui sont venues enrichir une documentation très détaillée, pour expliquer… comment écrire la documentation ! Nous avons intégré ce fichier à notre dépôt de code principal, aux côtés de nos autres ADRs.

Nous avions désormais une documentation OpenAPI composée d’un grand nombre de petits fichiers, qui était propre et nécessitait peu de travail pour fonctionner avec notre runtime et nos autres outils.

Un aperçu de la nouvelle structure de notre définition OpenAPI
La nouvelle structure de notre fichier racine OpenAPI, quelques milliers de lignes, et une utilisation massive des références vers des fichiers externes

Nous avions une branche avec notre nouvelle documentation, et il était temps de la merger. Le plus compliqué, c’était la communication et la planification. Nous avons dû bien préparer le terrain pour limiter les conflits dans les branches de code de nos collègues. Nous avons dû les former à l’utilisation du nouveau format de documentation et nous assurer que nos directives étaient connues et adoptées. Nous avons également écrit une petite commande CLI pour permettre aux ingénieurs de créer rapidement tous les fichiers nécessaires à la définition d’une nouvelle route API. Le processus s’est bien déroulé et nous avons accompagné les ingénieurs pendant quelques sprints à travers des sessions de pair programming, des vidéos tutorielles et de la documentation. Nous travaillons maintenant avec un schéma OpenAPI qui définit à l’heure actuelle :

  • 324 paths
  • 199 schemas réutilisables
  • 173 définitions de requêtes
  • 419 définitions de réponses
  • 212 paramètres
  • 105 tags

tout ça représente 1338 fichiers dans une hiérarchie de dossiers claire.

Dans un prochain article consacré à OpenAPI nous verrons comment nous avons exploité notre schéma OpenAPI pour tester notre API et générer du code dynamique.

TL;DR;

Notre histoire avec OpenAPI a commencé avec le besoin de documenter notre API, initialement sous la forme d’une documentation Swagger. En 2020, nous avons adopté la spécification OpenAPI avec l’aide d’une commande ad-hoc qui a généré le schéma à partir de la base de code. Cependant, au fil du temps, notre fichier OpenAPI initial de 7 000 lignes est devenu ingérable, atteignant jusqu’à 32 000 lignes et 240 définitions de routes de l’API, ce qui nous a conduits à découper notre monolithe, en utilisant le mécanisme de référence de la spécification OpenAPI. Ce nouveau format a considérablement amélioré la réutilisabilité et simplifié les revues de code. Bien que nous ayons rencontré de nombreux défis au cours de ce processus, notamment le sujet de la conception de bonnes conventions de nommage, le manque de support de l’écosystème, ou le retard par rapport aux dernières spécifications, le résultat final est inestimable, tant il simplifie la conception et la validation de l’API. Nous recommandons vivement de scinder votre schéma OpenAPI en plusieurs fichiers avant que le nombre de routes ne devienne trop important.

--

--