Transformez votre API Web en une API Hypermedia
Leonard Richardson a formulé en 2008 un modèle de maturité d’une API Web en 4 niveaux. Ce modèle est aujourd’hui une référence pour la communauté. Cependant, la majorité des acteurs restent bloqués au 3e niveau. Le 4e, intitulé “contrôles hypermedia”, est difficile à comprendre et sa valeur dans le Web service n’est a priori pas claire. Découvrez en quoi il consiste, et comment l’implémenter.
Si vous développez des API, vous avez probablement déjà rencontré le Richardson Maturity Model (RMM). Il simplifie l’approche permettant de créer une API REST. Le point de départ est le niveau 0, où HTTP n’est utilisé que comme un protocole de transport et sert à faire des appels de fonction à distance (Remote Procedure Call ou RPC). Chacune des étapes suivantes permet de se rapprocher d’une bonne implémentation de REST, fidèle à la thèse de Roy Fielding.
Vous avez peut-être aussi pensé que le niveau 3 était trop beau pour être vrai, qu’il était compliqué, vague, qu’il n’apportait pas grand-chose et que de toutes façons il est difficile à mettre en place. Bonne nouvelle : pas du tout ! Il y a de vrais avantages à utiliser ces fameux “contrôles hypermedia” et leur implémentation est plutôt facile. Et le tout sans casser vos clients existants !
HATEOAS
Pour atteindre le niveau 3 du RMM, il faut ajouter des contrôles hypermedia. Vous avez aussi peut-être rencontré l’acronyme HATEOAS, pour “hypermedia as the engine of application state”.
Pour l’expliquer plus simplement, il s’agit d’ajouter dans les réponses de votre API des liens que le client peut suivre pour atteindre ses buts. Un exemple simple : vous avez une ressource qui représente une commande de boisson. Au niveau 2, vous la représentez comme suit :
{
"drink": "tea",
"cost": 3.50,
}
Ce qui ne laisse à l’utilisateur aucun indice sur ce qu’il peut faire avec cette commande, il va donc devoir lire la documentation de votre API et implémenter sa requête suivante “en dur”. Pour être au niveau 3 de maturité, vous pouvez ajouter un lien vers l’étape suivante, comme ceci :
{
"drink": "tea",
"cost": 3.50,
"@controls": {
"self": {
"href": "http://api.coffee.com/orders/1234"
},
"pay": {
"href": "http://api.coffee.com/payment/order/1234",
"method": "PUT",
"template": {
"amount": 3.50
}
}
}
}
Le client peut alors suivre les indications données dans la réponse et payer sa commande.
Gains : découplage et affordance
Découplage
L’ajout de ces liens aux réponses permet un meilleur découplage entre le client et le serveur, au sens où le client a moins besoin de connaître la logique métier et les détails d’implémentation des fonctionnalités du serveur. On remplace la connaissance de tous les détails d’implémentation (URL et méthode) par la connaissance du seul nom de l’action.
D’autre part le client n’a plus à implémenter lui-même des vérifications de logique métier telles que des permissions ou des étapes de validation supplémentaires. Pour reprendre l’exemple précédent, si les serveurs doivent d’abord valider la commande avant de la payer, pas besoin d’intégrer un champ “statut” supplémentaire : s’il n’est pas encore possible de payer, le lien n’est simplement pas inclus dans la réponse.
Affordance
Une porte avec une bonne affordance : par la droite on tire et par la gauche on pousse.
Si vous n’avez jamais rencontré ce terme : il s’agit de la capacité d’un objet à suggérer l’utilisation qui peut en être faite.
Il s’agit aussi de ce que fait une API hypermedia : à chaque état, la réponse contient toutes les instructions pour passer aux états qui sont accessibles.
Cela permet à un développeur client de prendre en main beaucoup plus rapidement votre API, puisqu’il peut la découvrir comme un site web, en suivant les liens et avec quelques requêtes, plutôt qu’avoir à lire une longue documentation.
Ajouter des contrôles hypermedia en 3 étapes
Pour ajouter ces liens dans votre API, c’est relativement simple : il suffit de 3 étapes. D’abord, connaître les états possibles de vos ressources et les transitions possibles entre ces états. Ensuite il faut choisir le format de données pour représenter ces liens. Il y en a un certain nombre, donc le choix n’est pas facile ; la bonne nouvelle c’est qu’il y en a forcément un qui correspond à votre besoin ! Et il y a plusieurs façons de les comparer. Enfin, après ces deux étapes, il “suffit” de coder !
États et transitions : le diagramme d'état
La première chose à faire, c’est la liste des ressources. Une fois qu’elle est faite et connue, il faut savoir quels sont les états possibles de cette ressource. Ils peuvent être très simples (par exemple, une ressource peut être “en liste” ou “seule”) ou plus complexes, comme dans le cas de la commande de boisson (“créée”, “payée”, “annulée”, “servie”).
Enfin, il faut connaître les transitions possibles entre ces états. Il vaut mieux faire simple et représenter seulement les transitions “logiques”, comme pour une application. Pour continuer sur l’exemple du café, rien n’interdit à un client de créer une nouvelle commande alors qu’il est en train de payer la première ; mais cette transition est exceptionnelle et ce n’est pas la peine de la représenter.
Un bon outil pour visualiser ça : le diagramme d’état.
Choix d'un format
Difficulté récurrente pour les développeurs qui se sont essayés à l’hypermedia : le choix d’un format de données. Chacun couvre certains cas d’usage, pas tous, et il n’y a pas de standard clair qui se dégage. On assiste à ce phénomène :
source: xkcd: Standards, sous licence CC BY-NC 2.5
Voici une liste (non-exhaustive) des formats que nous avons pu comparer :
- Collection+JSON, créé par Mike Amundsen en 2011
- UBER, créé par Mike Amundsen en 2014
- HAL, créé par Mike Kelly en 2011
- Siren, créé par Kevin Swiber en 2012
- Mason, créé par Jorn Widlt en 2014
- JSON-LD, créé par Manu Sporny & al. en 2010
- Hydra, créé par Markus Lanthaler en 2012
- JSON-API, créé par Steve Klabnik en 2013
Popularité
Ces résultats sont à considérer avec précaution toutefois, car certains formats sont utilisés pour d’autres raisons que pour ajouter des liens hypermedia. Notamment, JSON-API est très utilisé simplement pour avoir une structure fixée à son API, et est imposée par le framework front-end Ember.js ; et JSON-LD a connu une hausse de popularité quand Google a suggéré d’inclure des objets dans ce format dans les pages Web pour améliorer leur référencement. D’autre part la popularité décroît vite si un format est récent : les résultats de Siren et Mason en découlent.
UBER est absent de ce graphique parce qu’il est très récent et donc peu utilisé, et d’autre part parce que son nom porte facilement à confusion et les recherches ont donc été trop difficiles.
La popularité est donc une information intéressante, mais pas assez sûre pour faire un choix. Heureusement, il y a d’autres options.
H-Factors
Mike Amundsen a proposé une façon de comparer les différents formats, en définissant ce qu’il a appelé les “H-Factors”. Il s’agit de différents types de contrôles hypermedia qui peuvent être présents ou pas dans une réponse d’API, et chaque format en supporte certains mais pas d’autres. Un schéma explicatif des H-Factors est présenté ci-dessous.
Une définition précise de ces facteurs est disponible ici : http://amundsen.com/hypermedia/hfactor/
Deux exemples simples :
- “LE” signifie “Embedded link” et désigne le fait qu’un format supporte une syntaxe spécifique pour indiquer qu’une ressource est censée être “inclue” dans une autre, comme la balise HTML <img href=”image.jpg”>.
- “CL” signifie “Link annotation control” et désigne le fait qu’un format supporte une syntaxe spécifique pour indiquer la sémantique d’un élément. HTML supporte ce facteur avec l’attribut “rel”, comme par exemple dans <link rel=”stylesheet”>.
Voici une comparaison des différents formats selon ce critère (une case est cochée si le format supporte le H-Factor en question, c’est-à-dire a spécifié une syntaxe permettant de reconnaître ce type de contrôle).
Format | H-Factors | ||||||||
LE | LO | LT | LN | LI | CR | CU | CM | CL | |
Collection+JSON | X | X | X | X | X | X | |||
UBER | X | X | X | X | X | X | X | X | X |
HAL | X | X | X | X | |||||
Siren | X | X | X | X | X | ||||
Mason | X | X | X | X | X | X | X | X | |
JSON-LD | X | ||||||||
Hydra | X | X | X | ||||||
JSON-API | X | X | X |
Point historique : Mike Amundsen a créé UBER après la création de cet outil de comparaison, dans le but d’avoir un format supportant tous ces facteurs.
Comparaison avec la même ressource
Représenter la même ressource dans tous les formats permet de voir les informations qu’on peut faire figurer dans la réponse, et aussi de comparer la “verbosité” des formats. Une telle comparaison (avec quelques commentaires) est disponible ici : https://github.com/Renaud8469/hypermedia-format-comparison
HAL et Mason ne sont pas trop verbeux, HAL reste simple tandis que Mason permet d’intégrer plus d’informations. Siren et Collection+JSON sont plus verbeux, mais leur structure fixe permet plus facilement d’implémenter des clients capables de reconnaître une nouvelle possibilité. (Plus de détails sur l’implémentation d’un client : RESTful API Clients)
Coder avec le pattern "représentant"
La dernière étape : coder. Pas si simple que ça, surtout si votre API est assez complexe. Une approche qui permet de faciliter cette phase d’implémentation : le design pattern “représentant”.
Rappel de REST : les serveurs et clients manipulent des ressources, et communiquent via des messages qui contiennent des représentations de ces ressources. Ce qu’on veut modifier pour y ajouter des contrôles hypermedia, ce ne sont pas les ressources, mais bien ces représentations.
Par conséquent, pas besoin de modifier beaucoup de chose au code interne de votre API ! Il suffit d’ajouter une couche, qui intercepte la réponse, lit la ressource envoyée, et génère une représentation de cette ressource dans le format que vous aurez choisi, avant de l’envoyer au client.
Pour savoir quelles transitions doivent faire partie de la réponse, une solution : les documenter une fois dans un fichier de configuration. Les informations que vous devrez renseigner :
- état de départ
- état d’arrivée
- url, méthode HTTP pour cette transition
- paramètres éventuels
Un exemple de transition ainsi documentée :
{
rel: "resource_list", // nom de la transition
target: "resource list", // état cible
accessibleFrom: [{ state: "home" }], // états depuis lesquels la transition est activable
href: "/resources",
method: "get"
}
Il vous faut ensuite intercepter également la requête pour savoir dans quel état est le client, pour ensuite chercher dans le fichier de configuration quelles transitions sont accessibles depuis cet état, et les incorporer à la réponse.
Diagramme récapitulatif (en rouge, les éléments à ajouter à votre API)
Cette architecture facilite également l’ajout d’un nouveau format de données, puisqu’il suffit d’ajouter le “traducteur” dans ce nouveau format (partie du code qui génère la réponse à partir des données et des transitions). Si vous travaillez en NodeJS, avec comme framework Express ou Hapi, un module NPM a été développé pendant ces travaux, et est disponible pour faciliter l’implémentation de l’intercepteur !
À vous !
Ajouter des contrôles hypermedia à votre API permet d’améliorer le découplage entre votre API et vos clients, ce qui vous sera bien utile quand vous voudrez faire des modifications. De plus, une telle API est plus facile à découvrir et plus interactive pour un nouveau développeur client. Et en utilisant le pattern “représentant” l’implémentation n’est pas si difficile ! Il suffit de bien définir les états de son application et de choisir un (ou plusieurs) formats de données. Alors, qu’est-ce que vous attendez ?