Défi­nir son API : version­ne­ment

Toujours dans la logique de réflé­chir son API, parce qu’un jour il faudra la faire évoluer, comment gérer le version­ne­ment ?

Plusieurs solu­tions ont émergé :

  • https://api-v2.example.com/mares­source
  • https://api.example.com/v2/mares­source
  • https://api.example.com/mares­source-v2
  • https://api.example.com/mares­source?v=2
  • https://api.example.com/mares­source avec une entête Version: 2
  • https://api.example.com/mares­source avec une entête Accept ou/et Content-type: appli­ca­tion/monfor­mat;version=2

La solu­tion du sous-domaine n’est à mon sens à réser­ver que pour les big-bang. Elle n’est pas faci­le­ment multi­pliable à l’in­fini, mais à l’avan­tage de permettre aisé­ment d’avoir même deux plate­formes tota­le­ment sépa­rées pour les deux versions de l’API.

Les deux suivantes se distinguent par le fait de version­ner l’API ou la ressource. J’ai tendance à penser que s’il faut version­ner la ressource en cassant la compa­ti­bi­lité, alors c’est peut être une nouvelle version de l’API qui est à publier si on ne veut pas finir avec un gros patch­work diffi­cile à main­te­nir : En gardant tout sous le même espace on s’in­ter­dit de faci­le­ment rendre obso­lète les anciennes versions.

Quitte à parfois devoir version­ner au niveau de la ressource, l’idée d’ajou­ter un para­mètre a fini par me sembler plus propre. Il s’agit quasi­ment toujours de s’adres­ser à une repré­sen­ta­tion diffé­rente de la ressource, pas de chan­ger son sens fonda­men­tal. Le risque est que la plupart des gens conti­nuent à utili­ser la version d’ori­gine et ne pas prendre en compte le para­mètre. Rendre obso­lète des anciennes repré­sen­ta­tions risque là aussi d’être diffi­cile.

Les possi­bi­li­tés d’ajou­ter les versions dans les entêtes sont souvent conseillées d’un point de vue théo­rique. En pratique mon retour est que c’est complexe à utili­ser, et d’une valeur ajou­tée assez discu­table. On oublie trop faci­le­ment que le bon usage de l’API tient direc­te­ment à sa simpli­cité et sa bonne compré­hen­sion. S’il y a besoin d’être expert en archi­tec­ture web pour comprendre le pourquoi des choses, c’est une mauvaise solu­tion. Le « tout dans l’URL » ajoute une faci­lité pour tester et échan­ger entre tech­ni­ciens qui vaut toutes les posi­tions acadé­miques du monde.

Twilio a aussi une façon inté­res­sante de gérer les versions. Au lieu d’un v2 ou v3, le déve­lop­peur indique une date et c’est le serveur qui sélec­tionne la version de l’API à utili­ser en fonc­tion de la date. C’est tentant, souple, mais j’ai peur que ce ne soit pas suffi­sam­ment expli­cite sur ce qu’on utilise ou sur comment gérer ce para­mètre. Qu’est-ce qui change si je met à jour la date ?

Des lectures et expé­riences je tire quelques recom­man­da­tions :

  • Prévoir dès le départ un système de version­ne­ment, vous en aurez besoin un jour, ne croyez pas que vos API pour­ront rester telles quelles ad vitam eter­nam
  • Impo­ser un version­ne­ment expli­cite, immé­dia­te­ment, dès la première version. Vous évite­rez les ambi­guï­tés et une partie des moules qui s’at­tachent aux adresses « sans version » par défaut
  • N’uti­li­ser 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 compa­tible c’est une nouvelle version et on incré­mente d’une unité. Le reste c’est du marke­ting et ça n’a rien à faire dans vos URLs.
  • Utili­ser un version­ne­ment dans l’URL, à la racine du service ; il sera temps d’uti­li­ser un autre sous-domaine si un jour il y a un vrai big bang qui le néces­site
  • Docu­men­ter (oui, c’est évident, mais pas si simple à ne pas oublier)

Publié

dans

par

Étiquettes :

Commentaires

9 réponses à “Défi­nir son API : version­ne­ment”

  1. Avatar de karl

    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.

    1. Avatar de edaspet
      edaspet

      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.

    2. Avatar de karl

      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.

    3. Avatar de edaspet
      edaspet

      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

    4. Avatar de karl

      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** ? :)

    5. Avatar de edaspet
      edaspet

      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.

  2. Avatar de karl

    hmm intéressant.

    fournir une liste de valeurs pour « rel » dans une documentation et dire de façon humaine quelle valeur correspond à quoi

    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.

    fournir l’adresse du point d’entrée en fonction de la version attendue pour commencer à découvrir l’API.

    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.

    déterminer le format de sortie du point d’entrée (ou la liste des « Accept » à laquelle il sait répondre)

    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.

    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)

    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.

    coder un robot qui fait l’exploration en question pour savoir quoi lancer suivant la ressource que tu demandes

    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.

    fournir une liste de liens dans une documentation et dire de façon humaine quel lien correspond à quoi

    oui on parle de la même chose, mais pas de la même façon.

    soit fournir une liste par version soit déterminer une racine ou un paramétrage systématique permettant de choisir la version/compatibilité

    Même chose.

    ç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.

    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.

    1. Avatar de edaspet
      edaspet

      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.

  3. Avatar de karl

    ah le « q » des commentaires n’est pas apparent dans la feuille de style ;)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *