L'approche REST

Quelques raccourcis :

Document en construction...

L'approche REST, pour Representational State Transfer, est le fruit des travaux de Roy Fielding, décrit dans sa thèse de 2000 nommée « Architectural Styles and the Design of Network-based Software Architectures », que vous pouvez consulter sur http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm. Pour nos besoins, le chapitre clé est le chapitre: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm

Idée de base

Avec l'importante expansion des possibilités programmatiques du Web au tournant du millénaire, différentes approches ont tour à tour été en vogue.

Approche Illustration

Programmer à l'aide de sockets bruts, ce qui fonctionne, est essentiellement universel, mais requiert un effort important. Développer des protocoles applicatifs est une opération périlleuse, qui force chaque individu à porter son attention sur des détails « de bas niveau » comme le signe des entiers, l'ordre des bytes dans un entier, l'encodage des chaînes de caractères, etc.

Exemple de sockets TCP

Exemple de sockets UDP (l'un, l'autre)

Adopter une approche RPC (Remote Procedure Call), où les messages circulant sur le réseau sont vus par les homologues de la communication comme des appels de fonctions. Ce style permet d'éviter de réfléchir aux détails de bas niveau susmentionnés, ceux-ci étant pris en charge par la plateforme choisie, mais introduit un couplage fort entre le code et la plateforme. Plusieurs intergiciels ont appliqué cette approche (COM, CORBA, ICE, etc.) au fil des ans

Composants (ici, )

Avec l'avènement des standards Web, qui constituaient un grand pas en avant vers une interopérabilité accrue, plusieurs approches RPC reposant sur des standards Web et profitant du format XML ont été mises de l'avant. La paire SOAP et WSDL, en particulier, fut en vogue pendant quelques années, et fut populaire à la fois en Java et sur la plateforme .NET; son prédécesseur plus simple, le protocole XML-RPC eut un peu moins de succès.

Notez que, bien que l'acronyme SOAP signifie Simple Object Access Protocol, ce protocole n'a vraiment rien de simple.

Comme pour les autres approches RPC, utiliser ces protocoles implique typiquement une chaîne d'outils qui génèrent des fichiers auxiliaires et prennent en charge le marshalling (transformation d'appels de fonctions en messages) et l'unmarshalling (transformation de messages en appels de fonctions), un couplage un peu lourd à mettre en place et à entretenir. L'idéal qu'était utiliser XML à titre de format universel de communication s'est aussi en partie buté à la complexité de SOAP, et l'interprétation de divers aspects du protocole par diverses implémentations a eu pour effet de compliquer l'atteinte tant convoitée de l'interopérabilité.

Je n'ai pas d'exemples simples de programme utilisant SOAP, car ceux-ci impliquent de nombreux outils tiers qui tendent à changer au fil du temps. Vous en trouverez toutefois dans Internet avec votre moteur de recherche favori

L'intérêt de la thèse de Roy Fielding, ce qui en explique d'ailleurs en partie l'impact, est qu'elle permet d'éliminer en grande partie ce couplage à des plateformes spécifiques et la complexité qui en découle. Utilisant le protocole http essentiellement à titre de protocole applicatif, et mettant en pratique quelques principes architecturaux sains, elle simplifie radicalement la conception de systèmes répartis pour le Web et nous rapproche collectivement de l'atteinte de l'interopérabilité.

Conception du style architectural

Suivons la ligne de pensée de Roy Fielding lui-même, qui présente REST comme un style hybride, et le construit par étapes. J'ai (exceptionnellement) débuté à 1 plutôt qu'à 0, dans le respect de la numérotation originale (§ 5.1.1-5.1.7).

Étape Détails

1

Null Style

Le point de départ de la thèse de Roy Fielding est ce qu'il appelle le Null Style. L'idée ici est qu'à ses yeux, un style architectural est un ensemble de contraintes, alors ce qu'il nomme le Null Style est tout simplement l'absence de contraintes. Un système Web sans contraintes est à ses yeux tel qu'il n'existe pas de frontière claire entre les composants qui constituent ce système.

C'est pour donner une forme plus claire, mieux définie à de tels systèmes que l'approche REST introduit des contraintes.

2

Client-serveur

Ce que Roy Fielding nomme « client-serveur » est une optique un peu limitée d'un vaste sujet. Au sens où il l'entend, la clé est de séparer les préoccupations des homologues d'une communication (en anglais : Separation of Concerns).

Il parle de l'importance de distinguer les préoccupations d'interface personne/ machine des préoccupations d'accès aux données, mais de manière plus large, il se préoccupe de la capacité des composants d'évoluer indépendamment les uns des autres. Ceci rejoint le principe d'encapsulation dans le monde OO, évidemment.

3

Sans états

Dans l'optique REST,  la communication doit se faire sans états. Cela signifie qu'il faut que chaque requête émise du client vers le serveur doit porter en elle toute l'information requise pour que le serveur puisse la traiter sans avoir d'informations a priori.

Cette contrainte apporte des avantages et des inconvénients. Pour les avantages, on trouve :

  • Une plus grande tolérance aux pannes. En effet, le serveur ne tenant pas à jour d'informations sur le client, il est remplaçable en cas de panne ou de bris
  • Une plus grande échelonnabilité, au sens où on peut ajouter des serveurs à loisir et où, le serveur n'ayant rien à entreposer entre deux traitements de requêtes, peut procéder plus rapidement
  • Une plus grande « visibilité », au sens où le serveur n'a qu'à examiner la requête pour comprendre ce qu'on attend de lui

Du côté des désavantages, on trouve :

  • Une plus grande consommation de bande passante, les requêtes étant plus imposantes et ayant tendance à répéter des informations
  • Une dépendance accrue du serveur envers la qualité de l'implémentation par les clients

Notez que http est un protocole sans états, ce qui explique en partie que le style REST soit construit sur la base de ce protocole.

4

Cache

Pour économiser de la bande passante et réduire les requêtes redondantes, il est raisonnable de permettre au client de conserver les résultats de certaines requêtes en antémémoire (en Cache). Le serveur doit pour sa part indiquer ce qu'il est raisonnable pour le client de conserver en antémémoire

5

Interface uniforme

En représentant les ressources et les services par des URI, on obtient une interface uniforme, ce qui facilite le développement à la fois d'outils et de code client

6

Système stratifié

Le client doit être agnostique face au nombre de strates entre lui et le serveur; l'idée est de permettre d'ajouter des strates, du Caching, de la sécurité, etc. de manière transparente

7

Code sur demande

Vu comme optionnel, le client est en droit de demander au serveur de lui retourner du code pour fins d'exécution sur le poste client

Vocabulaire

 

 

Exemples concrets

Pour créer un petit service REST en JavaScript, sans le moindre cadriciel (pour vraiment comprendre ce que cela implique), voir ../../Sources/service-REST-JavaScript-sans-cadriciel.html

Créer une API de type REST

Supposons une gamme de services permettant de manipuler des photos de chats d'un refuge. Une API de type REST pourrait prendre la forme suivante.

Notez que toutes les URI ci-dessous sont fictives.

Idée Verbe Exemples Détails

Identifier une URI racine, à partir de laquelle le service sera « ancré », en quelque sorte.

J'utilise ici /api/chats, mais ce qui peut servir de convention, mais REST n'impose pas de forme spécifique; il se peut, si vous utilisez un cadriciel ou un autre outil pour vous appuyer, que cela implique des contraintes de nommage

s/o

http://h-deb.clg.qc.ca/api/chats

 

Représenter les services d'obtention d'information par des requêtes appropriées, leur adjoignant des paramètres à même l'URI représentant le service.

Notez que le format vous appartient. Notez aussi que l'URI seule ne définit pas le service; c'est la paire faire de l'URI et du verbe qui définit de manière unique le service

GET

http://h-deb.clg.qc.ca/api/chats?

Obtenir de l'information sur le service

http://h-deb.clg.qc.ca/api/chats?nom="minou"&race="gouttiere"

Obtenir l'information sur un chat de gouttière nommé minou

http://h-deb.clg.qc.ca/api/chats?nombre

Obtenir le nombre de chats dans le refuge

POST

http://h-deb.clg.qc.ca/api/chats?nom="minou"&race="gouttiere"

Ajouter un chat de gouttière nommé minou au refuge

Une véritable API est typiquement bien plus vaste que ne l'est ce petit exemple, évidemment.

Parmi les recommandations préconisées pour les API de type REST, on trouve :

Privilégiez le recours à des noms. En gros, dans une approche REST, les verbes sont ceux de http; il est bien vu de privilégier des noms pour définir les services d'une API

Gardez les effets de bord au minimum. Visez des fonctions pures, soit des fonctions au sens mathématique du terme : pour les mêmes intrants, on devrait avoir les mêmes extrants. Évidemment, avec les verbes PUT, POST ou DELETE, une API de type REST ne sera pas nécessairement pure, mais c'est un objectif louable

Dans la même veine, visez des GET et des PUT qui soient idempotents

Visez une gamme de services cohérente et conséquente. Idéalement, les noms choisis pour l'API devraient faire un certain sens aux yeux des utilisatrices et des utilisateurs, et on devrait pouvoir deviner aisément comment réaliser des tâches avec une API donnée. On devrait aussi être capable de déduire facilement les données qui seront obtenues par une URI donnée. Notez qu'ici comme ailleurs, le nommage est une tâche difficile...

Si vous souhaitez une clientèle mobile, visez des services où les efforts demandés aux postes clients sont réduits au minimum

Il peut être sage d'inclure un numéro de version dans les URI d'une API. Ceci peut faciliter la coexistence de plusieurs versions de la même API, et aider à la migration éventuelle du code client d'une version antérieure vers une version plus récente

Une API de type REST fait normalement partie d'un système réparti. Conséquemment, il est préférable d'exposer une API de granularité grossière plutôt que fine. Entre autres, mieux vaut demander àux clients de fournir une information complète lors d'une requête, quitte à ce que le serveur n'utilise qu'une partie de cette information. N'oubliez pas que la bande passante et le réseau sont des ressources précieuses...

Si vous utilisez des marqueurs de date et d'heure, privilégiez le référentiel UTC (Greenwich), et laissez le client formater et adapter la présentation de cette informations aux usages et coutumes de l'usager. Les questions de fuseaux horaires et de gestion de l'heure avancée sont cauchemardesques...

Bien Bien (avec versions) Moins bien
GET    /bookmarks
POST   /bookmarks
GET    /bookmarks/{bookmarkId}
PUT    /bookmarks/{bookmarkId}
PATCH  /bookmarks/{bookmarkId}
DELETE /bookmarks/{bookmarkId}
GET    /bookmarks?theme="..."
GET    /v1/bookmarks
POST   /v1/bookmarks
GET    /v1/bookmarks/{bookmarkId}
PUT    /v1/bookmarks/{bookmarkId}
PATCH  /v1/bookmarks/{bookmarkId}
DELETE /v1/bookmarks/{bookmarkId}
GET    /v1/bookmarks?theme="..."
/getBookmarks
/createNewBookmark
/removeBookmark/{bookmarkId}
/addBookmark/{bookmarkId}
/changeBookmarkDate/{bookmarkId}

Falsehoods about names : https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/

Falsehoods about time : https://gist.github.com/timvisee/fcda9bbdff88d45cc9061606b4b923ca

Gérer les erreurs

Il est bien vu de faire correspondre les codes d'états retournés dans une réponse REST aux résultats du traitement. Cela dit, il y a beaucoup de codes d'états possibles dans http, et rares sont celles et ceux qui les connaissent tous suffisamment pour en tirer de l'information utile.

Les trois principales familles de codes d'états utiles sont :

Une recommandation fréquente est d'utiliser les codes 200, 400 et 500 (donc respectivement OK, Bad Request et Internal Server Error) puis d'ajouter un peu de finesse avec d'autres codes si ça semble pertinent, mais d'essayer de ne pas retourner plus d'une dizaines de codes d'états distincts au total pour une API donnée.

Certaines API (chez Facebook en particulier, apparemment; je n'ai pas vérifié) utilisent 200 même dans le cas d'une erreur, ce qui complique la vie du code client, qui dont alors détecter les problèmes par des moyens détournés (p. ex. : en examinant le corps de la réponse). Ne faites pas ça

Dans la famille 4xx, le code 400 est générique. Il est souvent utile de viser des codes un peu plus précis en fonction de l'erreur rencontrée pour aider le code client à comprendre la bourde qu'il vient de faire. En particulier, le célèbre 404 signifie, dans un contexte d'API de type REST, une incapacité à faire correspondre une URI à une ressource

Vu que les codes de la famille 5xx sont des erreurs côté serveur, il est raisonnable pour le client d'utiliser la même requête par la suite. Si vous retournez un code 5xx, il faut en être conscient.

Gestion standardisée -- RFC 7807

Plus en détail, le RFC 7807 (https://tools.ietf.org/html/rfc7807) dicte la marche à suivre officielle pour traiter des erreurs dans une API reposant sur http.

En particulier, ce RFC recommande un objet JSON représentant un descriptif détaillé de la raison derrière l'erreur (voir la section 3 de ce RFC : https://tools.ietf.org/html/rfc7807#section-3). Si vous documentez vos erreurs dans le respect de ce RFC, vous pouvez considérer votre stratégie de gestion d'erreurs comme étant conforme aux recommandations du IETF.

Les problèmes dans ces objets sont identifiés par champ décrivant le type de problème. Si les types recommandés ne conviennent pas à l'API que vous mettez de l'avant, la section 4 du RFC (https://tools.ietf.org/html/rfc7807#section-4) décrit une marche à suivre pour définir de nouveaux types de problème

Pour respecter ce RFC, il faut s'assurer que :

L'erreur soit un code d'état dans la famille 4xx ou dans la famille 5xx

Le type de contenu de la réponse doit être Content-Type application/problem, et doit sérialiser le contenu en format JSON ou en format XML

L'objet décrivant l'erreur ait la forme suivante :

Clé Valeur

detail (une chaîne de caractères)

Description textuelle du problème, destinée aux humains

type (une chaîne de caractères)

Une URI vers un document décrivant le problème en détail; ce document est destiné à consommation humaine

title (une chaîne de caractères)

Description sommaire et concise du type d'erreur (devrait être fixe pour un type donné), destinée aux humains

status (entier)

Le code d'état http correspondant à l'erreur; doit être le même que celui dans la réponse http

instance (une chaîne de caractères; optionnel)

Typiquement une URI; peut mener à un log des erreurs

Lectures complémentaires

Quelques liens pour enrichir le propos.

Généralités et pédagogie :

Technique et design :

Gestion des erreurs dans une API de type REST :

À propos de la relation entre REST et les bases de données avec opérations CRUD :

Critiques :

Alternatives :

Divers :


Valid XHTML 1.0 Transitional

CSS Valide !