# REST pragmatique

# REST pragmatique pour le projet Mojaloop

Avec l’émergence de la stratĂ©gie API comme levier de montĂ©e en charge pour les services Internet, l’attention portĂ©e aux technologies d’interconnexion a Ă©voluĂ©. S’appuyant sur les principes qui ont permis au Web de se structurer et de scaler, REST (Representational State Transfer) est devenu un choix de conception privilĂ©giĂ© pour les API de services Internet. Mais si les principes REST, proposĂ©s dans la thĂšse de doctorat de Roy Fielding qui les a dĂ©finis, ont une valeur acadĂ©mique pour la recherche, un design REST pur n’est pas aujourd’hui praticable pour la plupart des applications. Nous prĂ©conisons une forme de REST pragmatique — un patron de conception qui adopte les Ă©lĂ©ments utiles du design RESTful sans exiger une puretĂ© acadĂ©mique stricte.

# Le modÚle de maturité de Richardson

Martin Fowler a citĂ© un modĂšle structurĂ© d’adoption RESTful Ă©laborĂ© par Leonard Richardson et exposĂ© (opens new window) lors d’une confĂ©rence QCon. Fowler l’appelle le modĂšle de maturitĂ© de Richardson du design RESTful.

Martin Fowler, en rĂ©fĂ©rence Ă  Rest in Practice (opens new window)ÂČ, rĂ©sume ainsi la genĂšse du design RESTful :

utiliser des services web RESTful pour traiter une grande partie des problĂšmes d’intĂ©gration auxquels les entreprises sont confrontĂ©es. Au cƓur du propos se trouve l’idĂ©e que le Web est une preuve par l’existence d’un systĂšme distribuĂ© massivement scalable qui fonctionne trĂšs bien, et que nous pouvons en tirer des idĂ©es pour construire des systĂšmes intĂ©grĂ©s plus facilement.

Une approche pragmatique du design RESTful emprunte les meilleures parties du cadre conceptuel de Fielding pour permettre aux dĂ©veloppeurs et intĂ©grateurs de comprendre aussi rapidement que possible ce qu’ils peuvent faire avec l’API, sans avoir Ă  Ă©crire de code superflu.

Au plus fondamental, un design RESTful est centré sur les ressources et utilise les verbes HTTP. Au plus avancé, un design REST académique pur applique HATEOAS via des contrÎles hypermédia. Nous recommandons un design RESTful de niveau 2 pour Mojaloop.

# Pourquoi pas les contrÎles hypermédia ?

Bien que HATEOAS soit un principe fascinant — il prĂ©conise qu’un serveur rĂ©ponde Ă  chaque action client par une liste de toutes les actions possibles menant le client vers son prochain Ă©tat applicatif — et que les clients ne doivent pas s’appuyer sur des informations hors bande (comme une spĂ©cification API Ă©crite) pour savoir quelles actions sont possibles sur quelles ressources ni sur le format des URI.

C’est cette derniĂšre interdiction qui fait Ă©chouer le test du REST pragmatique : si HATEOAS est une approche thĂ©orique intĂ©ressante pour limiter le couplage, elle ne s’applique pas facilement Ă  Mojaloop (ni Ă  tout autre design d’API contractuelle). Si l’on considĂšre le public des API d’interconnexion, on trouve des acteurs commerciaux soumis Ă  des rĂšgles de schĂ©ma trĂšs prĂ©cises. Les interactions entre participants, et entre participant et hub de services central, sont fortement spĂ©cifiĂ©es pour fixer un risque commercial acceptable, pour proposer aux utilisateurs finaux des transactions tarifĂ©es Ă  trĂšs faible coĂ»t. Cela exige une prĂ©visibilitĂ© ex ante de l’API, incompatible avec le principe HATEOAS dĂ©fini par Fielding.

# Principes REST pragmatiques

# Les URI définissent les ressources

Un schĂ©ma d’URI bien conçu rend une API facile Ă  consommer, Ă  dĂ©couvrir et Ă  Ă©tendre, comme une API soignĂ©e dans un langage classique. Le REST pur dĂ©daigne ce principe au profit de HATEOAS. Mais le REST pragmatique suit un schĂ©ma d’URI habituel pour faciliter la comprĂ©hension humaine, mĂȘme si des principes HATEOAS sont employĂ©s pour la dĂ©couverte.

Les chemins d’URI qui dĂ©signent une collection d’objets doivent ĂȘtre un nom pluriel, p. ex. /customers, pour un ensemble de clients. Lorsqu’une collection ne peut avoir qu’une seule instance, on utilise le singulier pour Ă©viter la confusion. P. ex. GET /transfers/:id/fulfillment est correct, car il n’y a qu’un objet fulfillment par transfert identifiĂ©.

Les chemins d’URI qui dĂ©signent un objet unique doivent ĂȘtre un nom pluriel (la collection) suivi d’un identifiant unique prĂ©dĂ©fini. P. ex. /customers/123456 pour le client numĂ©ro 123456. L’identifiant doit ĂȘtre unique dans la collection et persister pendant la vie de l’objet dans cette collection. Les identifiants ne doivent pas ĂȘtre des valeurs ordinales — un parcours ordinal dans une collection se fait via des paramĂštres de requĂȘte sur l’URI de collection.

Les chemins d’URI peuvent avoir un prĂ©fixe pour l’environnement, la version ou un autre contexte de la ressource. AprĂšs le chemin d’identification, il ne doit suivre que des collections et des rĂ©fĂ©rences d’objets.

Les identifiants de segments de chemin et de requĂȘte doivent ĂȘtre choisis dans l’ensemble des caractĂšres romains [0-9A-Za-z]. Utiliser camelCase pour les Ă©lĂ©ments du chemin d’URI. Ne pas utiliser snake_case.

Pour lever toute ambiguĂŻtĂ©, le caractĂšre « _ » (tiret bas) et « - » (tiret) ne doivent pas ĂȘtre utilisĂ©s dans les identifiants de segments de chemin ou de requĂȘte.

Cela peut sembler un peu Ă©troit. L’objectif est d’avoir un format d’URI bien dĂ©fini, cohĂ©rent avec les usages rĂ©pandus, simple Ă  dĂ©crire, prĂ©visible, et qui se mappe aux environnements et conventions natifs. Cela ne satisfera pas tout le monde. Voici la logique de cette contrainte :

CapitalCase et camelCase sont la norme de facto pour NodeJS et JavaScript et une contrainte courante pour les URI : les segments de chemin sont souvent mappés vers des ressources internes JS ; respecter les conventions JS est cohérent.

Les noms de champs en JSON et SQL doivent suivre la mĂȘme convention, car ils sont souvent mappĂ©s automatiquement vers l’espace de noms des variables et peuvent ĂȘtre rĂ©fĂ©rencĂ©s dans les URI comme segments de chemin ou de requĂȘte.

Il faut aussi Ă©viter « $ » sauf si une bibliothĂšque l’exige (p. ex. jQuery). Le JCL IBM est rĂ©volu ; laissons-le en paix. Il existe de meilleurs outils de portĂ©e pour sĂ©parer les espaces de noms que d’introduire des symboles non romains.

Il faut Ă©viter « - » (tiret) dans les noms de segments de chemin et de paramĂštres de requĂȘte car cela ne se mappe pas aux noms de variables, SQL ou champs JSON.

Les caractĂšres tiret bas doivent ĂȘtre Ă©chappĂ©s dans le source Markdown en les prĂ©fixant par « \ ».

On a signalĂ© que snake_case serait lĂ©gĂšrement plus lisible que camelCase dans les noms de variables, mais cela n’amĂ©liore pas la lisibilitĂ© des URI : cela gĂȘne visuellement la lecture des dĂ©limiteurs de segments de chemin et de requĂȘte. Et lorsque les URI sont soulignĂ©es Ă  l’affichage, les tirets bas deviennent illisibles.

# Paramùtres d’URI

Utiliser un ensemble standard et prévisible de paramÚtres optionnels de maniÚre cohérente.

Un ensemble standard de paramĂštres de requĂȘte doit servir pour les collections afin de laisser l’appelant contrĂŽler la portion de collection retournĂ©e. « count » pour dĂ©finir le nombre d’objets Ă  retourner, « start » pour dĂ©finir le point de dĂ©part dans le jeu de rĂ©sultats, et « q » comme requĂȘte de recherche libre. Nous dĂ©finirons l’ensemble standard au fil du temps et l’appliquerons de façon uniforme.

# Verbes

Les objets singuliers doivent prendre en charge GET pour la lecture, PUT pour le remplacement complet (ou la création lorsque la clé primaire est fournie par le client et persistante, p. ex. un PAN de carte de paiement), et DELETE pour la suppression.

Les collections doivent prendre en charge GET pour lire tout ou partie d’une collection, et POST pour ajouter un nouvel objet à la collection.

Les objets singuliers peuvent prendre en charge POST pour modifier leur Ă©tat de façon dĂ©terminĂ©e. Poster un document JSON vers l’URI d’un objet singulier peut mettre Ă  jour des champs sĂ©lectionnĂ©s ou dĂ©clencher un changement d’état ou une action sans remplacer tout l’objet.

GET doit ĂȘtre implĂ©mentĂ© de maniĂšre nullipotente — c’est-Ă -dire que GET ne provoque jamais d’effets de bord ni ne modifie l’état visible par le client hors journalisation ou mise Ă  jour des mĂ©triques d’instrumentation.

PUT et DELETE doivent ĂȘtre implĂ©mentĂ©s de maniĂšre idempotente — les changements s’appliquent de façon cohĂ©rente aux donnĂ©es du systĂšme en ne dĂ©pendant que de l’état de la ressource et des entrĂ©es, rien d’autre. L’action n’a pas d’effet supplĂ©mentaire si elle est rĂ©pĂ©tĂ©e avec les mĂȘmes paramĂštres et ne dĂ©pend pas de l’ordre d’autres opĂ©rations sur une collection ou d’autres ressources. Par exemple, retirer une ressource d’une collection peut ĂȘtre idempotent sur la collection. Utiliser PUT pour remplacer (ou crĂ©er) entiĂšrement une ressource identifiĂ©e de façon unique lorsque l’URI est entiĂšrement connue du client est aussi idempotent. Le systĂšme peut donc rĂ©ordonner les opĂ©rations pour gagner en efficacitĂ© ; le client n’a pas besoin de savoir si la ressource existe avant de tenter un remplacement.

POST et PATCH ne sont pas des opĂ©rations idempotentes. POST sert Ă  crĂ©er des ressources dont l’identifiant est assignĂ© par le serveur ou lorsqu’une ressource interne unique est impliquĂ©e par l’URI cible (p. ex. POST /transfers, mais PUT /transfers/:id/fulfillment). Voir la note 3 (RFC 5789).

# Format des données

Nous privilégions les formats liés à JSON (opens new window) plutÎt que XML (voir note 4). Dans certains cas, les formats seront binaires ou XML, selon des normes préexistantes, et seront précisément spécifiés. Les formats binaires doivent avoir une syntaxe formelle pour éviter des ambiguïtés de représentation (jeux de caractÚres, représentations big-endian ou little-endian des valeurs numériques, etc.).

Les dates et heures utilisĂ©es dans les API doivent respecter la norme ISO 8601, avec le profil du document W3C sur les formats date et heure (voir note 5). Cette note W3C doit rĂ©duire la complexitĂ© et les erreurs lorsque des composants Ă©changent des dates et heures concrĂštes. Il existera des cas oĂč un format non ISO sera requis par une norme externe, p. ex. dates d’expiration ISO 7813.

Les formats XML standard existants doivent disposer d’un schĂ©ma XSD pour le sous-ensemble de profil acceptable dans le projet. Pour des formats particuliĂšrement complexes, on peut utiliser un traducteur de profil commun pour mapper entre le sous-ensemble projet du format standard et le format fil utilisĂ© par un protocole normalisĂ©. Cela limite le couplage aux formats complexes de façon plus maintenable.

Lorsque l’action PATCH est spĂ©cifiĂ©e pour une ressource, nous utiliserons un format de document de patch cohĂ©rent (p. ex. JSON Patch (opens new window), voir note 6).

# Codes de retour

Utiliser les codes HTTP de façon cohérente et conformément à leurs définitions standard. Les codes standard sont définis dans la RFC 2616 (voir note 7).

# Format d’erreur lisible par machine

L’API doit fournir un rĂ©sultat d’erreur lisible par machine dans un format JSON bien dĂ©fini. {À dĂ©finir : enveloppe de rĂ©ponse et format des erreurs, dĂ©fauts et enveloppes de succĂšs. Le design RESTful s’appuie sur les en-tĂȘtes pour les erreurs de protocole ; les infos de dĂ©bogage peuvent aussi transiter dans les en-tĂȘtes. Il faut ĂȘtre clair sur l’usage d’une enveloppe et comment elle soutient la communication production normale entre client et serveur.}

# Versionnement

Les URI d’API doivent inclure un identifiant de version au format vM comme premier segment de chemin (oĂč M est la composante majeure du numĂ©ro de version). L’API et son identifiant de version doivent respecter la spĂ©cification versionnement sĂ©mantique (opens new window) 2.0 pour le versionnement d’API (voir note 8).

Un client doit indiquer le numĂ©ro de version majeure dans chaque requĂȘte. Un client ne peut pas exprimer l’exigence d’une version mineure prĂ©cise.

Le numĂ©ro de version complet de l’API est indiquĂ© dans l’en-tĂȘte de rĂ©ponse (À dĂ©finir) pour toutes les rĂ©ponses rĂ©ussies et en erreur.

Bien que le contrat de version d’une API soit influencĂ© par les niveaux majeur, mineur et correctif, seul le numĂ©ro majeur lie l’API en production — un client de production ne peut pas demander une version mineure ou un niveau de correctif particulier, et un serveur de production n’accepte pas une requĂȘte URI qui spĂ©cifierait ces Ă©lĂ©ments supplĂ©mentaires.

En revanche, dans les environnements de prĂ©production, on prĂ©voit qu’une combinaison de suffixes mineur, correctif, prĂ©-release et mĂ©tadonnĂ©es puisse ĂȘtre prise en charge dans les requĂȘtes client (comme dĂ©fini dans semver [3]) et peut figurer dans les URI de prĂ©production pour faciliter le dĂ©veloppement et l’intĂ©gration.

# Il sera peut-ĂȘtre temps de mettre REST de cĂŽtĂ©

En concevant les API d’interconnexion entre composants et systĂšmes participants, nous pourrions rencontrer des exigences qui ne correspondent pas exactement au modĂšle REST pragmatique dĂ©fini ici. Nous Ă©valuerons au cas par cas et choisirons ce qui sert le mieux les objectifs du projet.

# Exigences non fonctionnelles

En développant les API, nous ferons des choix cohérents sur les exigences non fonctionnelles pour renforcer les objectifs du projet.

1: http://martinfowler.com/articles/richardsonMaturityModel.html, consulté le 18 août 2016.

2: https://www.amazon.com/gp/product/0596805829 (opens new window), consulté le 18 août 2016.

3: RFC 5789, PATCH Method for HTTP, https://tools.ietf.org/html/rfc5789 (opens new window), consulté le 18 août 2016.

4: Introducing JSON, http://json.org/ (opens new window), consulté le 18 août 2016.

5: http://www.w3.org/TR/1998/NOTE-datetime-19980827 (opens new window), consulté le 22 août 2016.

6: JSON Patch, http://jsonpatch.com/ (opens new window), consulté le 18 août 2016.

7: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html (opens new window)

8: Semantic Versioning 2.0.0, http://semver.org/ (opens new window), consulté le 18 août 2016.