Versioning de services REST
Introduction
Le versioning de service est un thème à part entière dans les architectures orientée service tant il structure l'évolutivité d'un service. Les architectures REST n'échappent pas à la règle. Posant les principes généraux de l'architecture, Roy Fielding n'en a pas pour autant décrit tous les méandres. Il n'appartient donc qu'à nous, développeurs et architectes, de définir ce qu'est le versioning des services REST en respectant ces principes. Ce billet traitera donc la problématique de versioning des services REST.
Quand versionne-t-on ?
Les types de changement
Il existe différents types de changements
- On peut être amené à modifier des règles métiers soit parce qu’elles évoluent, soit parce qu’elles sont « bugfixées ».
- On peut être amené à enrichir la signature du service pour répondre à de nouveaux besoins.
Souvent le premier cas n’a pas d’impact sur la signature du service et nous le laisserons volontairement en dehors du scope de cet article même si quelques problématiques de versioning se posent.
Dans le second cas, les évolutions de signature (c'est-à-dire principalement des paramètres d’appels et de réponse) peuvent être soit compatible avec les versions antérieures soit incompatibles.
Changement compatible
Ce sont les changements qui respectent le principe de compatibilité ascendante. Ces changements n’ont aucun impact sur les clients existants, pour peu qu’ils soient correctement implémentés.
C'est-à-dire qu’il respecte les pratiques suivantes :
Le client ne doit pas tenir compte de l’ordre des éléments dans une représentation (XML par exemple)
Le client ne doit pas échouer lorsque des éléments sont rajoutés dans une représentation (XML par exemple)
Le client ne doit pas échouer lorsque de nouveaux codes retours sont ajoutés
Le client d’une représentation ne doit pas échouer lorsqu’une nouvelle représentation est ajoutée
Par exemple, pour une ressource Album représentée en XML :
<album>
<title>Dark Side of the Moon</title>
</album>
L’ajout d’un champ ne modifie en rien la structuration précédente du message.
<album>
<title>Dark Side of the Moon</title>
<artist>Roger Waters</artist>
</album>
Les clients existants ne doivent pas être impactés.
Les changements incompatibles
Les changements dit incompatibles impactent directement les clients existants. Sur l’exemple précédent, si un ou plusieurs clients utilisent l’élément « artist », le fait de passer d’un artiste par album à une liste d’artistes par album rend cet élément inutilisable par ces clients.
<album>
<title>Dark Side of the Moon</title>
<artists>
<artist>Roger Waters</artist>
<artist>David Gilmour </artist>
</artists>
</album>
Un client, même codé selon les règles de l’art du parsing XML, ne pourra pas absorber la modification. (nom, granularité, type, …)
Quand versionner une représentation ?
Les changements compatibles correspondront donc à un changement mineur, les changements incompatibles seront des changements majeurs. Ce sont ces derniers qui obligent une montée de version du service. On changera donc de version lorsque qu’un changement de comportement du service entraîne :
- un changement incompatible
- la création d’un lien vers une nouvelle ressource
- la dépréciation d’une ressource.
Le versioning en REST
Il existe plusieurs moyens de versionner.
Le versioning via URL
La première réponse à ce problème de versioning a été de rajouter un paramètre identifiant la version dans l’URL de la ressource.
Par exemple la version 1 d’une ressource album avec une représentation XML :
GET http://.../v1/albums/12133
<album>
<title>The dark side of the moon</title>
<artist>The pink floyds</artist>
</album>
Et la version 2 de la même ressource album avec une représentation XML aussi :
GET http://.../v2/albums/12133
<album>
<title>The dark side of the moon</title>
<artists>
<artist>Roger Waters</artist>
<artist>David Guilmour</artist>
<artist>Nick Mason</artist>
<artist>Rick Wright</artist>
</artists>
</album>
Ce versioning est inspirée du modèle SOAP où la version est portée dans l’adressage du service. A chaque nouvelle version d’un service on définie une nouvelle URL et un nouveau schéma WSDL associé.
Cette approche ne nous satisfait pas sur plusieurs points :
Rupture avec les principes REST :
La plus importante est que cette approche est en rupture avec un des principes de REST : une ressource est identifiée par une URI sous entendu durable et stable dans le temps L’introduction de la version dans l’URI implique de définir une URI différente par version de la ressource maintenue par le serveur. Dans l’exemple précédent deux URI permettent d’accéder à la ressource Album.
De plus cette approche donne l’impression aux utilisateurs que l’album dans une version de l’API est une ressource différente du même album dans une autre version. Hors, ce qui a changé ici n’est pas la ressource mais une de ces représentations (la représentation au format XML pour être complet).
Exposition de paramètres techniques :
Cette approche rend visible directement au client une partie de la complexité de la gestion des versions de l’API. En effet, nous faisons apparaître dans l’URI un paramètre purement technique, le numéro de version, alors que l’URI représente la dénomination fonctionnelle et unique de la ressource.
Best practice REST
« Content negotiation »
Une autre approche qui s’appuie sur le mécanisme de négociation de contenu du protocole http réponds entre autre à ces limitations. Revenons en deux mots sur la négociation de contenu : ce mécanisme permet à un client d’une ressource web de spécifier quelle(s) représentation(s) il préfère obtenir.
Techniquement parlant cette information se trouve dans la requête http dans une entête appelée Accept header. Voici un exemple de requête avec négociation de contenu :
GET http://... /albums/12133
Accept : text/xml, application/json
…
Le serveur répondra de préférence un fragment XML, s’il le gère, une structure de données JSON dans le cas contraire. Les types définis dans l’Accept header peuvent être soit des media types standardisés soit des types personnalisés.
Versioning par Accept
Selon la philosophie REST le client spécifie dans sa requête la représentation de la ressource à laquelle il souhaite accéder. Ceci est fait au travers du mécanisme de négociation de contenu explicité ci-dessus.
Ce mécanisme permet donc de véhiculer les informations liées aux différentes versions de l’API. Le client spécifie dans le Accept Header quelles sont les versions d’une représentation d’une ressource qu’il souhaite avoir, par ordre de préférence.
La gestion des versions de service REST au travers de ce mécanisme a un prérequis : l’introduction de media types non standard.
Si nous reprenons l’exemple du versioning précédent les types créés seront :
application/vnd.octo.music-v1+xml associé au schéma XML de la version 1 de la représentation XML de l’album
application/vnd.octo.music-v2+xml associé au schéma XML de la version 2 de la représentation XML de l’album
Par exemple la requête pour accéder à la version 1 de la ressource album avec une représentation XML :
GET http://.../albums/12133
Accept : application/vnd.octo.music-v1+xml
<album>
<title>The dark side of the moon</title>
<artist>The pink floyds</artist>
</album>
Et la requête pour accéder à la version 2 de la ressource album avec une représentation XML :
GET http://.../albums/12133
Accept : application/vnd.octo.music-v2+xml
<album>
<title>The dark side of the moon</title>
<artists>
<artist>Roger Waters</artist>
<artist>David Guilmour</artist>
<artist>Nick Mason</artist>
<artist>Rick Wright</artist>
</artists>
</album>
Conclusion
La principale solution apportée au problème de versioning de services REST a été d’introduire le numéro de version dans l’URI. Nous lui préférons la solution utilisant la négociation de contenu et le versioning de représentations plus que ressources. Ceci pour plusieurs raisons :
Le versioning via URI enfreint un des principes fondamentaux de REST : Une URI Une ressource
Dans la philosophie REST les échanges sont pilotés par le client, c’est le client qui sait quelle représentation il peut manipuler. Ceci autant pour le format des données (xml, json, html, …) que pour la version de ces données. Ces deux cas doivent être traités au travers du même mécanisme : la négociation de contenu et le champ Accept Header.
La négociation de contenu permet au client de définir les versions de représentation qu’il souhaite recevoir par ordre de préférence
Les principaux framework REST offrent des fonctionnalités natives de gestion de contenu coté client et serveur
Dans un article futur nous pourrons décrire l’architecture technique/applicative impliquée par la gestion de versions via Accept Header. Nous pourrons ainsi répondre aux questions : Comment mettre à disposition une nouvelle version d’un service ? Comment supporter coté serveur deux versions majeures d’un service en même temps ? Comment connaître les versions utilisées par les clients du service ?
Benoît Guillou & Benjamin Magnan