Changements Majeurs Mauvais ! La Versionnage d'API est Bon !
Comme quiconque ayant construit ou utilisant régulièrement une API le réalise tôt ou tard, les changements majeurs sont très mauvais et peuvent être une très sérieuse tache sur une API autrement utile. Un changement majeur est un changement dans le comportement d'une API qui peut briser l'intégration d'un utilisateur et entraîner beaucoup de frustration et une perte de confiance entre le fournisseur d'API et l'utilisateur. Les changements majeurs nécessitent que les utilisateurs soient informés à l'avance (avec des excuses accompagées) plutôt qu'un changement qui apparaîtrait sans avertissement, tel qu'une nouvelle fonctionnalité agréable. La manière d'éviter cette frustration est de versionner une API avec des assurances de la part du propriétaire de l'API qu'il n'y aura pas de changements surprenants introduits au sein d'une seule version.
Alors, à quel point peut-il être difficile de versionner une API ? La vérité est que ce n'est pas difficile, mais ce qui est difficile, c'est de maintenir un certain bon sens en évitant de se déployer inutilement dans un nombre vertigineux de versions et de sous-versions appliquées sur des dizaines de points de terminaison API avec des compatibilités peu claires.
Nous avons introduit la v1 de l'API il y a trois ans et nous n'avons pas réalisé qu'elle continuerait à fonctionner aussi bien jusqu'à ce jour. Alors, comment avons-nous réussi à fournir la meilleure API de livraison d'emails pendant plus de deux ans tout en maintenant la même version d'API ? Bien qu'il existe de nombreuses opinions différentes sur la versionnage des APIs REST, j'espère que l'histoire de notre humble mais puissante v1 pourra vous guider sur votre chemin vers l'illumination en matière de versionnage d'API.
REST est Meilleur
L'API SparkPost provient de notre époque où nous étions Message Systems, avant nos aventures dans le cloud. À l'époque, nous étions occupés à faire les préparations finales pour le lancement beta de Momentum 4. C'était une mise à niveau majeure par rapport à la version 3.x, notre MTA leader sur le marché. Momentum 4 incluait une toute nouvelle interface utilisateur, des analyses en temps réel, et surtout, une nouvelle API web pour l'injection et la génération de messages, la gestion des modèles et l'obtention de métriques d'email. Notre vision était celle d'une architecture d'abord API – où même l'interface utilisateur interagirait avec des points de terminaison d'API.
Une des décisions les plus précoces et meilleures que nous avons prises était d'adopter un style RESTful. Depuis la fin des années 2000, le transfert d'état représentatif (REST) basé sur APIs web est le standard de facto des APIs cloud. Utiliser HTTP et JSON facilite la tâche des développeurs, peu importe quel langage de programmation ils utilisent – PHP, Ruby, et Java – pour intégrer notre API sans avoir à connaître ou se soucier de notre technologie sous-jacente.
Choisir d'utiliser l'architecture RESTful était facile. Choisir une convention de versionnage ne l'était pas tant. Au départ, nous avons évité la question du versionnage en ne versionnant pas du tout la beta. Cependant, dans quelques mois, la beta était entre les mains de quelques clients et nous avons commencé à construire notre service cloud. Il était temps de versionner. Nous avons évalué deux conventions de versionnage. La première consistait à mettre le versionnage directement dans l'URI et la seconde consistait à utiliser un en-tête Accept. La première option est plus explicite et moins compliquée, ce qui est plus facile pour les développeurs. Comme nous adorons les développeurs, c'était le choix logique.
Gouvernance d'API
Avec une convention de versionnage sélectionnée, nous avions plus de questions. Quand devrions-nous augmenter la version ? Qu'est-ce qu'un changement majeur ? Allions-nous re-versionner toute l'API ou juste certains points de terminaison ? Chez SparkPost, nous avons plusieurs équipes travaillant sur différentes parties de notre API. Au sein de ces équipes, des personnes travaillent sur différents points de terminaison à différents moments. Par conséquent, il est très important que notre API soit cohérente dans l'utilisation des conventions. C'était plus grand que le versionnage.
Nous avons établi un groupe de gouvernance comprenant des ingénieurs représentant chaque équipe, un membre de l'équipe de gestion des produits, et notre CTO. Ce groupe est responsable d'établir, documenter, et faire respecter nos conventions d'API à travers toutes les équipes. Un canal Slack de gouvernance d'API est également utile pour des débats animés sur le sujet.
Le groupe de gouvernance a identifié un certain nombre de moyens par lesquels des changements peuvent être introduits dans l'API qui sont bénéfiques pour l'utilisateur et ne constituent pas un changement majeur. Ceux-ci incluent :
Une nouvelle ressource ou un point de terminaison API
Un nouveau paramètre optionnel
Un changement à un point de terminaison API non public
Une nouvelle clé optionnelle dans le corps POST JSON
Une nouvelle clé retournée dans le corps de réponse JSON
A l'inverse, un changement majeur incluait tout ce qui pouvait briser l'intégration d'un utilisateur, tel que :
Un nouveau paramètre requis
Une nouvelle clé requise dans les corps de POST
La suppression d'un point de terminaison existant
La suppression d'une méthode de requête de point de terminaison existante
Un comportement interne matériellement différent d'un appel API – comme un changement dans le comportement par défaut.
Le Grand 1.0
Alors que nous documentions et discutions ces conventions, nous sommes également parvenus à la conclusion qu'il était dans l'intérêt de tout le monde (y compris le nôtre !) d'éviter de faire des changements majeurs à l'API puisque la gestion de plusieurs versions ajoute pas mal de surcharge. Nous avons décidé qu'il y avait quelques choses que nous devions corriger avec notre API avant de nous engager à "v1".
Envoyer un simple email nécessitait trop d'efforts. Pour "garder les choses simples", nous avons mis à jour le corps POST pour garantir que les cas d'utilisation simples et complexes soient pris en compte. Le nouveau format était plus à l'épreuve du futur également. Deuxièmement, nous avons traité un problème avec le point de terminaison des Métriques. Ce point de terminaison utilisait un paramètre "group_by" qui modifiait le format du corps de réponse GET de sorte que la première clé serait la valeur du paramètre de regroupement. Cela ne semblait pas très RESTful, nous avons donc dissocié chaque regroupement dans un point de terminaison séparé. Enfin, nous avons audité chaque point de terminaison et apporté de petits changements ici et là pour garantir qu'ils soient conformes aux standards.
Documentation Précise
Il est important d'avoir une documentation API précise et utilisable pour éviter les changements majeurs, qu'ils soient délibérés ou non. Nous avons décidé d'utiliser une approche simple de documentation API en nous appuyant sur un langage Markdown appelé API Blueprint et gérer nos docs dans Github. Notre communauté contribue et améliore ces documents open source. Nous maintenons également un ensemble non public de docs dans Github pour des APIs et des points de terminaison internes.
Au départ, nous publiions nos docs sur Apiary, un excellent outil pour le prototypage et la publication de docs d'API. Cependant, l'intégration d'Apiary dans notre site web ne fonctionne pas sur les appareils mobiles donc nous utilisons maintenant Jekyll pour générer des docs statiques à la place. Nos derniers docs API SparkPost se chargent maintenant rapidement et fonctionnent bien sur les appareils mobiles, ce qui est important pour les développeurs qui ne sont pas toujours assis devant leur ordinateur.
Séparer le Déploiement de la Publication
Nous avons appris tôt la précieuse astuce de séparer un déploiement d'une publication. De cette façon, il est possible de déployer fréquemment des changements quand ils sont prêts grâce à la livraison et au déploiement continus, mais nous n'annonçons pas toujours ou ne documentons pas publiquement ces changements en même temps. Il n'est pas rare que nous déployions un nouveau point de terminaison API ou une amélioration à un point de terminaison API existant et de l'utiliser à partir de l'interface utilisateur ou avec des outils internes avant de le documenter publiquement et de le soutenir. De cette façon, nous pouvons apporter quelques ajustements pour la convivialité ou la conformité aux standards sans nous soucier de faire un changement majeur redouté. Une fois que nous sommes satisfaits du changement, nous l'ajoutons à notre documentation publique.
Oups !
Il est juste d'admettre qu'il y a eu des moments où nous n'avons pas respecté nos idéaux de "pas de changements majeurs" et ceux-ci valent la peine d'être appris. Une fois, nous avons décidé qu'il serait préférable pour les utilisateurs qu'une certaine propriété soit par défaut à vrai au lieu de faux. Après avoir déployé le changement, nous avons reçu plusieurs plaintes de la part des utilisateurs car le comportement avait changé de manière inattendue. Nous avons annulé le changement et ajouté un paramètre au niveau du compte – une approche beaucoup plus conviviale, c'est sûr.
Occasionnellement, nous sommes tentés d'introduire des changements majeurs en raison de corrections de bogues. Cependant, nous avons décidé de laisser ces idiosyncrasies de côté plutôt que de risquer de briser les intégrations des clients au nom de la cohérence.
Il existe des cas rares où nous avons pris la décision sérieuse de faire un changement majeur – comme la dépréciation d'une ressource API ou d'une méthode – dans l'intérêt de la plus grande communauté d'utilisateurs et seulement après avoir confirmé qu'il y a peu ou pas d'impact sur les utilisateurs. Par exemple, nous avons délibérément choisi de modifier le comportement de réponse de l'API de Suppression mais uniquement après avoir soigneusement pesé les avantages et les impacts sur la communauté et soigneusement communiqué le changement à nos utilisateurs. Cependant, nous ne ferions jamais introduire un changement qui aurait une possibilité éloignée d'impacter directement l'envoi d'un email de production d'un utilisateur.