Je regarde un peu les implémentations hypermedia pour une API. J’avoue que je pleure un peu. Qu’on soit clairs, JSON n’est pas adapté pour ça, sauf à construire des messages bien complexes à lire et à produire (ce qui tue un peu l’utilité de JSON). Maintenant si on veut garder JSON, voilà l’état de mes reflexions :
— Ce billet est en évolution constante, dernière mise à jour le 18 juin 2013 au matin —
Déjà quelques specs liées :
- URI Template : RFC6570
- Web linking : RFC5988
- Profile pour les types de liens : Draft draft-wilde-profile-link-04
- JSON-PATCH : RFC6902
- Syntaxe réduite pour les liens : Curie
Et deux discussions à lire en amont (avec les commentaires, pour les deux) :
- Linking in JSON, Mark Nottingham
- Getting hyper about hypermedia APIs, David Heinermeier Hansson
JSON API
- Spec assez simple
- Utilise URI Template
- Réserve un terme trop générique (links) pour les clefs de la racine JSON
- Ne permet pas d’utiliser des URI pour les types de relation
- Ne permet pas d’avoir plusieurs liens (de format différent par exemple) pour une même relation
- Le type de ressource ciblée par une relation peut être spécifié (ce qui me parait superflu : si on connait le sens de la relation, on connait le type ciblé)
- Impose JSON-PATCH
HAL – Hypertext Application Language
- Rendu assez moche (oui, c’est subjectif mais ça compte)
- Gère des espaces de noms via Curie dans les liens et relations
- Utilise (optionnellement) les URI Template (mais ne précise pas où trouver les paramètres)
- Permet de préciser un profile pour qualifier les attributs d’un objet (mais pas de mixer plusieurs vocabulaires)
- Ne permet pas d’avoir plusieurs liens (de format différent par exemple) pour une même relation
- Beaucoup de bibliothèques de code dans pas mal de langages, côté client et côté serveur
- J’échoue complètement à séparer ce une collection ou un attribut complexe et une ressource embarquée, donc à comprendre l’utilité de la clef _embedded
- C’est en draft IETF
JSON-LD
- Semble être fait par des gens avec un esprit assez proche de RDF
- Simple à lire, mais trop complète, trop complexe, risque d’être difficile à implémenter
- Gère un des vocabulaire avec des URI pour qualifier les liens, les clefs, etc. avec même des possibilités d’alias
- Considérant les indirections possibles, trouver le lien avec une valeur spécifique de « rel » est une tâche assez complexe
- Ne gère pas de template pour les liens, mais sait gérer les liens relatifs à une base déclarée plus haut (ce qui compense un peu)
- C’est en voie de standardisation au W3C
- On peut ajouter Hydra par dessus pour décrire les actions (petite présentation)
- Peu d’implémentations clientes (trouvé une PHP et un convertisseur vers RDF en Ruby)
Siren
- Va plus loin que les autres, en décrivant aussi les types d’actions possibles sur la ressources décrite, les paramètres pour les différentes actions, etc. (illusion de pouvoir coder un navigateur d’API générique ?)
- Pas de template pour les liens
- Simple à comprendre et à relire (si on met de côté la partie « actions »)
- Impose une enveloppe, les clefs à la racine sont liées à Siren, pas à l’objet décrit
- Pourquoi ici encore séparer les entités liées des propriétés ?
Collection/JSON
Quand on pense avoir saturé, on se rend compte que ce n’est pas fini. J’ai donc trouvé Collection+JSON après les autres. Il permet de définit des gabarit d’attributs pour les ressources, ajoute les liens et les rôles des liens, la notion de collection, et définit les mécanismes d’ajout/recherche.
C’est finalement une de celes qui se concentrent le mieux sur la tâche fixée au départ, mais je ne suis pas certain d’être convaincu. Au moins on a évité la sur-ingénierie. C’est implémentable de bout en bout.
Quelques pré-conclusions
Certaines spécifications vont bien trop loin à mon goût, et pas toujours en sachant faire correctement la base (éviter les conflits de nommage, pouvoir utiliser des URI pour le vocabulaire, voire des espaces de noms).
Rien que les templates d’URI sont finalement inutiles. Ils permettent de grappiller quelques octets en évitant de taper des liens complets, mais imposent de rédiger un code non négligeable rien que pour reconstruire le lien final, et empêchent de copier un sous-objet en le considérant comme autonome (il ne l’est pas, le template pour son URL est dans l’objet parent).
Alors parler de décrire les actions et les interactions possibles avec chaque objet… En voulant trop en faire on reporte beaucoup de complexité sur le client. On est parfois en train de faire des clients très complexes pour éviter de gérer des informations simples et qu’on va probablement de toutes façons coder en dur quelque part. Ça en devient contre-productif. De ce côté j’apprécie la petitesse de JSON-API.
J’ai encore l’avis qu’imaginer un client HATEOAS complet et générique est illusoire. J’ai juste besoin d’attacher quelques métadonnées comme des liens, des rôles aux liens, et éventuellement un vocabulaire pour les types de données.
Et puis, sérieusement, si c’est pour que le résultat ne soit plus éditable agréablement et présente des structures non naturelles, quel est l’intérêt d’être passé à JSON ?
XML, ou même HTML avec des microdata/formats est définitivement plus adapté que JSON pour ce que je cherche à faire. À JSON il manque au moins un moyen d’attacher des métadonnées à une clef/valeur (par exemple la notion d’attribut sur les nœuds XML/HTML). Avec ça nous pourrions faire un joli format, sans ça ça restera bien moche et peu pratique.
Le problème c’est que développer un client qui fouille des données HTML avec une structure lâche à base de microdata/formats, c’est aussi assez complexe à gérer. Reste le XML « à la main » mais si si je veux que mon API soit utilisée, je crains que JSON ne soit le seul choix pragmatique.
Entre les différentes spécifications
Mon cœur balance entre JSON-API pour sa simplicité (mais réserver le terme « links » me semble juste une aberration), HAL parce qu’il semble de loin (mais ce « _embedded » me gêne toujours, et faire un « _links »: { « self »: { « href »: « xxxxxx » } } juste pour donner le lien du sous-objet courant me inutilement lourd), et JSON-LD parce que ça ressemble assez fort à la façon dont je pense et que ça semble permettre tout ce que je peux imaginer d’intelligent (mais implémenter la spec complètement risque d’être franchement difficile).
Dans une précédente version de ce billet j’ai tenté un subset de JSON-LD où n’importe quel objet peut contenir :
- un attribut (facultatif) @id, qui est le lien identifiant l’objet
- un attribut (facultatif) @type, qui définit l’URL du type de l’objet (par exemple un type de schema.org) et potentiellement le sens des sous-clefs ou sous-liens.
- un sous-objet (facultatif) @context, qui contient les différents préfixes et adresses utilisables pour les différentes clefs de l’objet (afin de pouvoir mixer plusieurs vocabulaires sans risquer de conflits de noms et de sens).
- un sous-objet (facultatif) @rel, inexistant dans JSON-LD, qui pointe les différents objets liés par leur rôle (attribut « rel » habituel) pour les retrouver facilement (il y a trop d’indirections pour ça dans JSON-LD)
Mais je n’aime pas réinventer la roue, et aussi moche soit-il, HAL contient peut être le principal. Entre une spécification géniale mais trop complexe et sans implémentation cliente, et une spécification plus simple, moche mais bourrée d’implémentations, j’ai du mal à ne pas recommander la seconde. J’ai quand même toujours un peu de mal à voir comment me servir utilement du _embedded.
Laisser un commentaire