Toujours dans la logique de réfléchir son API, parce qu’un jour il faudra la faire évoluer, comment gérer le versionnement ?
Plusieurs solutions ont émergé :
- https://api-v2.example.com/maressource
- https://api.example.com/v2/maressource
- https://api.example.com/maressource-v2
- https://api.example.com/maressource?v=2
- https://api.example.com/maressource avec une entête Version: 2
- https://api.example.com/maressource avec une entête Accept ou/et Content-type: application/monformat;version=2
La solution du sous-domaine n’est à mon sens à réserver que pour les big-bang. Elle n’est pas facilement multipliable à l’infini, mais à l’avantage de permettre aisément d’avoir même deux plateformes totalement séparées pour les deux versions de l’API.
Les deux suivantes se distinguent par le fait de versionner l’API ou la ressource. J’ai tendance à penser que s’il faut versionner la ressource en cassant la compatibilité, alors c’est peut être une nouvelle version de l’API qui est à publier si on ne veut pas finir avec un gros patchwork difficile à maintenir : En gardant tout sous le même espace on s’interdit de facilement rendre obsolète les anciennes versions.
Quitte à parfois devoir versionner au niveau de la ressource, l’idée d’ajouter un paramètre a fini par me sembler plus propre. Il s’agit quasiment toujours de s’adresser à une représentation différente de la ressource, pas de changer son sens fondamental. Le risque est que la plupart des gens continuent à utiliser la version d’origine et ne pas prendre en compte le paramètre. Rendre obsolète des anciennes représentations risque là aussi d’être difficile.
Les possibilités d’ajouter les versions dans les entêtes sont souvent conseillées d’un point de vue théorique. En pratique mon retour est que c’est complexe à utiliser, et d’une valeur ajoutée assez discutable. On oublie trop facilement que le bon usage de l’API tient directement à sa simplicité et sa bonne compréhension. S’il y a besoin d’être expert en architecture web pour comprendre le pourquoi des choses, c’est une mauvaise solution. Le « tout dans l’URL » ajoute une facilité pour tester et échanger entre techniciens qui vaut toutes les positions académiques du monde.
Twilio a aussi une façon intéressante de gérer les versions. Au lieu d’un v2 ou v3, le développeur indique une date et c’est le serveur qui sélectionne la version de l’API à utiliser en fonction de la date. C’est tentant, souple, mais j’ai peur que ce ne soit pas suffisamment explicite sur ce qu’on utilise ou sur comment gérer ce paramètre. Qu’est-ce qui change si je met à jour la date ?
Des lectures et expériences je tire quelques recommandations :
- Prévoir dès le départ un système de versionnement, vous en aurez besoin un jour, ne croyez pas que vos API pourront rester telles quelles ad vitam eternam
- Imposer un versionnement explicite, immédiatement, dès la première version. Vous éviterez les ambiguïtés et une partie des moules qui s’attachent aux adresses « sans version » par défaut
- N’utiliser que des numéros de version simples, pas de notion de mineur/majeur, pas de points ou virgules : Si ça change de façon non compatible c’est une nouvelle version et on incrémente d’une unité. Le reste c’est du marketing et ça n’a rien à faire dans vos URLs.
- Utiliser un versionnement dans l’URL, à la racine du service ; il sera temps d’utiliser un autre sous-domaine si un jour il y a un vrai big bang qui le nécessite
- Documenter (oui, c’est évident, mais pas si simple à ne pas oublier)
9 réponses à “Définir son API : versionnement”
L’enjeu principal que je vois, c’est le tight-coupling qui est réalisé au niveau de l’URI comme postulat de départ. Juste pour l’exercise imagine un instant que tu interdis le couplage version-URI et que tu réfléchisses en termes de Quelle information le serveur et le client communique pour comprendre les évolutions de l’API.
GET /
dans le contenu (je ne présuppose pas du format) tu récupères un
rel: version avec un lien vers une URI
Cette URI est une collection (liste) avec les différents liens vers les évolutions de l’API.
Autre possibilité est d’associer des valeurs permettant au client sur un type de resources données de comprendre une partie de l’API et pas forcément tout.
Typiquement qu’est-ce que le client peut faire de façon autonome, découvrir sans que le développeur n’est à tout reprogrammer. Ne pas oublier aussi les codes HTTP, et les redirections.
Je ne suis pas certain que les deux entrent en conflit. Quelle que soit la solution choisie, tu peux utiliser ton système de découverte hypermédia.
Si je veux qu’on puisse aussi utiliser l’API via ses URI directement, c’est parce que au final pour l’humain, ça reste quand même plus simple à tester, découvrir, utiliser.
Les deux n’entrent pas tout à fait en conflit. En revanche l’un conditionne l’autre. Tu peux commencer un tableau en dessinnant le premier plan et en remplissant les trous avec le fond. Mais cela devient une contrainte très difficile. Il est mieux de partir d’un système qui permet de découvrir l’API et où justement les URIs ne sont « pas connus » car cela évitera justement d’avoir à rentrer dans le problème de version.
Je retourne au premier point de mon commentaire. Juste pour le jeu. Oublie toute notion d’URI construit dans un premier temps, et imagine quelle est l’information contenue dans le message échangée et pourquoi cela pêterait un vieux client quand cette information évolue.
à lire aussi http://www.mnot.net/blog/2011/10/25/web_api_versioning_smackdown
Ceci dit sans cas concret de domaine, il est également difficile de discuter des solutions à envisager.
Je serai tenté de dire « ça ne me suffit pas ». HATEOS ne me dit rien de comment je gère mon changement de version quand je change les valeurs de « rel », la façon dont je renvoie les liens vers les autres ressources, les paramètres que j’attends, etc.
La découverte de l’API nécessite déjà elle-même une API
Mais il n’y a pas de formule magique. Je ne comprends pas.
link[@rel], a[@rel] même si tu changes les valeurs de « rel » ne cassent pas ton client. Ton client est toujours capable d’explorer ton API et donc son évolution.
Peut-être parles-tu alors de la signification de la valeur rel associée à une action, mais ce n’est pas ce que propose HTTP qui s’occupe de l’information. Bien sûr la plupart des gens exposent la logique interne de leurs services (qui est hors HTTP) au niveau du réseau et ensuite reproche à HTTP de ne pas faire ce que le programme fait. :) Ironie.
Quel est le problème que tu tentes de résoudre du côté **client** ? :)
Je ne reproche pas à HTTP de se décharger du sens des valeurs de rel, ou de comment coder les liens (link et a sont en html, mais dans d’autres formats c’est moins évident). Ce que je dis c’est que la valeur ajoutée de l’exploration par les liens me semble assez surestimée.
Là tu dois :
* fournir une liste de valeurs pour « rel » dans une documentation et dire de façon humaine quelle valeur correspond à quoi
* fournir l’adresse du point d’entrée en fonction de la version attendue pour commencer à découvrir l’API (ou si tu veux faire plus complexe : donner un point d’entrée et déterminer comment sélectionner les liens avec une métadonnée qui correspond à leur compatibilité/version)
* déterminer le format de sortie du point d’entrée (ou la liste des « Accept » à laquelle il sait répondre)
* déterminer comment on code les liens hypermédia dans ce format (pour html on a « link » et « a », pour d’autres soit il n’existe rien de standard soit il y a beaucoup de choix et il faut sélectionner) et comment on lit le « rel » (équivalent d’un attribut si ça n’existe pas dans le format de sortie)
* coder un robot qui fait l’exploration en question pour savoir quoi lancer suivant la ressource que tu demandes
Pour beaucoup de besoins je trouve que
* fournir une liste de liens dans une documentation et dire de façon humaine quel lien correspond à quoi
* soit fournir une liste par version soit déterminer une racine ou un paramétrage systématique permettant de choisir la version/compatibilité
est finalement aussi simple et pas beaucoup moins compatible.
Dans les deux cas l’étape réellement sensible est la première. Le robot du premier cas apporte une indirection en plus, ça permet de faire évoluer les URLs à contenu identique sans faire de redirection mais c’est un besoin qui apparait finalement assez rarement sur une API.
Tu as définis une grammaire pour trouver les liens mais vu que le choix du lien reste soumis à une liste exhaustive et codée « en dur », sauf à utiliser des valeur de « rel » relativement standard (et donc pas spécifiques à l’API), la valeur ajoutée me semble assez faible.
hmm intéressant.
ou permettre de le découvrir par l’API. Il s’agit là de la documentation de l’API. La différence est que tu ne te retrouve pas prisonnier des URIs que tu as définies initialement. Alors que tu peux avoir un rel contenant deux valeurs ou plus rel= »fou barre ». Disons 1-1.
Oui même chose avec la documentation de l’API, il faudra bien donner l’adresse de la documentation pour la coder dans le client :) 1-1.
Oui même chose pour la façon dont les APIs sont conçus habituellement. Tu trouves dans la documentation les formats de sorties. 1-1.
Ah bah non. :) C’est sûr qu’utiliser un format non hypermedia pour faire une API hypermedia cela pose des problèmes. Il y a les efforts pour rendre hypermedia JSON. Je ne suis pas sûr de la pertinence mais au moins des personnes essaient. Donc les deux formats hypermedias disponibles pour l’instant sont Atom et HTML. Note aussi qu’il y a la découverte possible de
Link: <uri>; rel="blablabla"
. Là cela revient au choix de la techno de départ. Tu n’as pas peut-être pas besoin de 1. Hypermedia 2. ni même de HTTP. :) C’est pour cela que j’ai posé la question à propos des clients. Et puis la question réelle de ton billet sur le versionning. En environnement social distribué, le versionning ne fonctionne pas sauf si tu controlles vraiment le client.l’exploration non. l’interprétation des valeurs oui. mais qui sont détachées des identifiants. En quelque sorte tu changes le versionning du côté du client. C’est le serveur qui s’adapte au client, plutôt que le client qui doit s’adapter au serveur.
oui on parle de la même chose, mais pas de la même façon.
Même chose.
Donc elle n’évolue pas cet API. :) Mais que fait Ducros ?
Donc je comprends ce que tu dis et je ne pense pas qu’il y ait beaucoup de différences. Merci de prendre le temps d’expliquer un peu plus.
Je pense que ceci est le plus proche de ce que tu veux réaliser
https://api.example.com/maressource avec une entête Accept ou/et Content-type: application/monformat;version=2
MAIS je pense également que tu penses trop au serveur à la place de penser au client. Je serais curieux de savoir comment tu envisagerais le problème avec le client contrôlant la démarche. Imagine un client V1 accédant à des informations pour réaliser quelquechose, comment mon client V2 peut réagir à serveur V1 ayant évoluer. Ce n’est pas tout à fait le même enjeu. Le client initie la requête, le serveur ne fait que répondre.
ah, peut être qu’un contexte à prendre en compte est que je réfléchissais à une api fournie par mes serveurs et uniquement eux. Du coup un client V2 n’a jamais à attaquer un serveur V1. Le serveur est forcément en avance (ou à égalité) avec le client.
Ca explique peut être aussi que même si je pense au client, l’évolution je la vois par le serveur.
À un moment si le client veut pouvoir interpréter une évolution de mon API, il doit recevoir la doc correspondante et changer le jeu d’url n’est pas une manipulation vraiment complexe. Pour dire vrai j’ai déjà des indirections de découvertes dans une API, le /me permet de découvrir quelles URLs sont à utiliser pour la suite (chaque client a sa version de la ressource, donc son url). Au final personne ne l’a jamais utilisée, même lors des développements initiaux : ils ont directement fouillé dans les exemples pour récupérer l’url probable et l’utiliser.
ah le « q » des commentaires n’est pas apparent dans la feuille de style ;)