BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Introduction aux principes REST

Introduction aux principes REST

Favoris

Vous ne le savez peut-être pas, mais ces derniers temps la "meilleure" manière d'implémenter un mode de communication inter-applications hétérogène fait débat.

Alors que la pratique la plus répandue repose principalement sur des web-services basés sur SOAP, WSDL et l'univers des spécifications WS-*, une petite mais très vocale minorité soutient qu'il existe une meilleure alternative: REST (REpresentiational State Transfer, soit Transfert d'Etat REprésentatif).

Dans cet article, nous allons essayer de présenter une introduction pragmatique à REST et l'intégration du RESTful HTTP aux applications sans entrer dans ce débat. Nous irons dans les détails en expliquant les aspects qui, selon mon expérience, causent le plus de discussion quand quelqu'un est confronté à cette approche pour la première fois.

Principes Clés de REST

La plupart des présentations de REST commencent par une définition formelle et un historique. Je vais laisser cela pour plus tard et fournir une définition pragmatique: REST est un ensemble de principes qui définissent comment les standards du Web, tels que HTTP et les URIs, sont supposés être utilisés (ce qui diffère assez de ce que beaucoup de gens font réellement).

La promesse est que si vous adhérez aux principes REST en concevant votre application, vous obtiendrez un système capable de tirer parti de l'architecture du Web. En résumé, les cinq principes clés sont:

  • Donner un ID à toute "élément"
  • Lier les éléments entre eux
  • Utiliser les méthodes standard
  • Avoir des représentations multiples pour les ressources
  • Communiquer sans état

Regardons chacun de ces principes de plus près.

Donner un ID à tout "élément"

J'utilise ici le terme "élément" plutôt que le plus formellement correct "ressource" car c'est un principe tellement simple qu'il ne devrait pas être éclipsé par de la terminologie.

Si vous étudiez les systèmes que les gens construisent, il existe généralement un ensemble d'abstractions clés qui méritent d'être identifiées. Tout ce qui devrait être identifiable devrait de manière évidente avoir un identifiant. Sur le Web, il existe un concept unifié pour les IDs: l'URI. Les URIs constituent un espace de nommage global, et utiliser les URIs pour identifier vos ressources clés signifie qu'elles ont un identifiant unique et global.

L'avantage principal d'une stratégie de nommage cohérente est que vous n'avez pas à concevoir vos propres stratégies - vous pouvez vous reposer sur une stratégie qui a déjà été bien définie, fonctionne vraiment bien à large échelle et est comprise par pratiquement n'importe qui. Si vous considérez un objet de haut-niveau arbitraire dans la dernière application sur laquelle vous avez travaillé (en supposant que ce n'était pas une application REST), il est très probable qu'il y existe de nombreux cas d'utilisation pour lesquels vous auriez pu tirer parti de cela. Si votre application incluais une abstraction d'un client par exemple, je suis raisonnablement sûr que les utilisateurs auraient aimé pouvoir envoyer un lien vers un client spécifique par email à un collaborateur, créer un marque-page vers celui-ci dans leur navigateur, ou même le noter sur un bout de papier. Pour marquer la pertinence de ce point, imaginez quelle décision affreuse pour les affaires cela serait pour un magasin en ligne tel qu'Amazon.com de ne pas identifier chacun de ses produits avec un ID unique (une URI).

Quand ils sont confrontés à cette idée, beaucoup de gens se demandent si cela signifie qu'ils devraient exposer leurs entrées de base de donnée (ou leur IDs) directement - et sont souvent repoussés par cette simple idée, puisque des années de pratiques dans le monde de l'orienté-objet nous ont appris à cacher les aspects persistence, en tant que détails d'implémentation. Mais ce n'est pas du tout conflictuel: habituellement les ressources - les "éléments" - qui méritent d'être identifiés par une URI sont bien plus abstraits qu'une entrée de base de donnée. Par exemple, une ressource représentant une commande pourrait être composée de produits, d'une adresse et de beaucoup d'autres aspects que vous ne voulez pas forcément exposer en tant qu'éléments identifiables distinctement. L'idée d'identifier les éléments qui le méritent mène de plus à la création de ressources que l'on ne vois pas habituellement dans la conception d'une application typique: une procédure (ou une étape), une vente, une négociation, une demande de devis - ce sont tous des exemples de "choses" qui méritent une identification. Cela peut à son tour mener à la création d'entités plus persistantes que dans un design non-RESTful.

Voici quelque exemples d'URIs que vous pourriez obtenir:

  • http://example.com/customers/1234
  • http://example.com/orders/2007/10/776654
  • http://example.com/products/4554
  • http://example.com/processes/salary-increase-234

Comme j'ai choisi de créer des URIs pouvant être facilement interprétées par un humain - un concept utile, bien qu'il ne soit pas un pré-requis pour un design RESTful - il devrait être assez facile de déduire leur signification. Elles identifient évidemment des éléments individuels. Mais jettez un oeil aux suivantes:

  • http://example.com/orders/2007/11
  • http://example.com/products?color=green

Au premier abord, celles-ci paraissent différentes - après tout, elles n'identifient pas des éléments uniques mais plutôt des collections d'éléments (en supposant que la première URI identifie toutes les commandes passées en Novembre 2007, et la seconde l'ensemble des produits de couleur verte). Mais ces collections sont en fait des ressources elles-même, et elles méritent tout à fait d'être identifiées.

Notez que les avantages d'avoir une stratégie de nommage unifiée et globale s'appliquent à la fois à l'usage du Web dans votre navigateur et à la communication entre machines.

Pour résumer le premier principe: utilisez des URIs pour identifier tout ce qui mérite d'être identifiable, spécifiquement toutes les ressources de "haut-niveau" que votre application fourni, qu'elles représentent des éléments individuels, des collections d'éléments, des objets virtuels ou physiques, ou encore des résultats de calculs.

Lier les éléments entre eux

Le principe auquel nous allons nous intéresser maintenant a une description formelle qui est un peu intimidante: "l'Hypermédia en tant que moteur de l'état applicatif", parfois abbrévié HATEOAS ("Hypermedia As The Engine Of Application State", ça ne s'invente pas!). Au coeur de ce principe, il y a les concepts d'hypermédia, ou en d'autres mots l'idée de liens. Les liens nous sont familiers grâce au HTML, mais ils ne se limitent aucunement à une utilisation par les êtres humains. Considérez le fragment fictif d'XML suivant:

<order self='http://example.com/customers/1234' >
    <amount>23</amount> 
    <product ref='http://example.com/products/4554' /> 
    <customer ref='http://example.com/customers/1234' /> 
</order>

Si vous regardez les liens de produit et de client dans ce document, vous pouvez facilement imaginer comment une application qui l'a récupéré peut "suivre" les liens pour obtenir des informations supplémentaires. Bien sûr, ce serait aussi le cas s'il y avait un simple attribut "id" adhérant à une stratégie de nommage spécifique à l'application - mais seulement dans le contexte de la dite application. La beauté de l'approche des URIs est que les liens peuvent pointer vers des ressources fournies par une application tierce, un autre serveur, ou même une entreprise différente sur un autre continent - parce que la stratégie de nommage est un standard global, toutes les ressources qui constituent le Web peuvent être liées entre elles.

Il y a un aspect encore plus important au principe d'hypermédia - la partie "état" de l'application. En résumé, le fait que le serveur (ou le fournisseur de service, si vous préférez) fournisse un ensemble de liens au client (le consommateur de services) permet au client de changer l'état de l'application en suivant un lien. Nous allons nous intéresser aux effets de cet aspect dans un article à venir; pour le moment, gardez juste à l'esprit que les liens sont une manière extrêmement pratique de rendre une application dynamique.

Pour résumer ce principe: Utilisez des liens pour référer à des ressources identifiables chaque fois que possible. Les Hyperliens sont ce qui font du Web le Web.

Utilisez les méthodes standard

Dans la discussion autour des deux premiers principes, il y avait une supposition implicite: que l'application peut réellement faire quelque chose qui ai du sens avec les URIs. Si vous voyez une URI écrite sur le flanc d'un bus, vous pouvez l'entrer dans le champ adresse de votre navigateur et appuyer sur Entrée - mais comment votre navigateur sait-il quoi faire avec l'URI?

Il sait quoi faire avec parce que chaque ressource a la même interface, le même ensemble de méthodes (ou opérations si vous préférez). HTTP appelle cela des verbes, et en plus des deux que tout le monde connait (GET et POST), l'ensemble des méthodes standard inclut PUT, DELETE, HEAD et OPTIONS. La signification de ces méthodes est décrite dans la spécification HTTP, ainsi que certaines garanties quand à leur comportement. Si vous êtes un développeur Orienté Objet, vous pouvez vous imaginer que chaque ressource dans un scénario HTTP-RESTful étends une classe comme celle-ci (en pseudo-code proche du Java/C#, se concentrant sur les méthodes clés):

class Resource {
     Resource(URI u);
     Response get();
     Response post(Request r);
     Response put(Request r);
     Response delete();

Parce que la même interface est utilisée pour toutes les ressources, vous savez être capable d'en récupérer une représentation en utilisant GET. Parce que la sémantique de GET est définie dans la spécification, vous pouvez être sûr qu'il n'y a aucun pré-requis en appellant la méthode - c'est pourquoi celle-ci est dite "sécurisée". GET permet une mise en cache très efficace et sophistiquée, de telle manière que dans la plupart des cas vous n'avez même pas à envoyer une requête au serveur. Vous pouvez aussi être sûr qu'un GET est idempotent - si vous envoyer un GET et n'obtenez pas de résultat, vous ne pourrez peut-être pas savoir si c'est parce que la requête n'a jamais atteint sa destination ou si c'est la réponse qui s'est perdu sur le chemin du retour. La garantie d'idempotence signifie que vous pouvez tout simplement renvoyer la même requête une seconde fois (les résultats ne se cumulent pas). L'idempotence est aussi garantie pour les requêtes PUT (qui signifient "met à jour cette ressource avec ces données, ou crée-la à cette URI si elle n'existe pas déjà") et pour les requêtes DELETE (vous pouvez essayer et réessayer jusqu'à obtenir un retour, supprimer quelque chose qui n'est pas là n'est pas un problème). POST, qui signifie habituellement "crée une nouvelle ressource", peut aussi être utilisé pour invoquer des processus arbitraires et n'est donc ni sécurisé, ni idempotent.

Si vous exposez les fonctionnalités de votre application (ou les fonctionnalités du service si vous préférez) d'une manière RESTful, ce principe et ses restrictions s'appliquent aussi à vous. Cela peut être difficile à accepter si vous êtes habitués à une autre manière de concevoir votre application - après tout, il y a de fortes chances que vous soyez convaincu que votre application a beaucoup trop de logique métier pour pouvoir tout exprimer sous forme d'une poignée d'opérations. Laissez-moi un peu de temps pour essayer de vous convaincre que ce n'est pas le cas.

Considérez l'exemple suivant d'un scénario simple d'approvisionnement:

Vous pouvez constater qu'il y a ici deux services définis (sans impliquer de technologie particulière pour l'implémentation). L'interface de ces services est spécifique à la tâche en cours - il s'agit d'un service de commande (OrderManagement) et d'un service clientèle (CustomerManagement) dont nous parlons. Si un client veux consommer ces services, il doit être codé en fonction de cette interface particulière - il n'est pas possible d'utiliser un client qui a été codé avant que ces interfaces aient été établies pour pouvoir interagir avec elle de manière utile. Les interfaces définissent le protocole applicatif des services.

Dans une approche HTTP RESTful, vous auriez à vous contenter de l'interface générique qui constitue le protocole HTTP. Vous pourriez obtenir quelquechose comme ceci:

Vous pouvez voir que ce qui a fut des opérations spécifiques d'un service a été mappé sur les méthodes HTTP standard - et pour couper court à tout ambiguïté, j'ai créé tout un univers de nouvelles ressources. J'entends un "C'est de la triche!". Non. Un GET sur une URI qui identifie un client est tout aussi significatif qu'un getCustomerDetails. Certaines personnes ont un triangle pour visualiser cela:

Imaginez les trois sommets comme des poignées que vous pouvez tourner. Vous pouvez voir que dans la première approche, vous avez beaucoup d'opérations et beaucoup de types de données et un nombre fixe d'"instances" (en substance, autant que de services). Dans la seconde, vous avez un nombre fixe d'opérations, beaucoup de types de données et beaucoup d'objets sur lesquels invoquer ces quelques méthodes. Au final, cela illustre que vous pouvez en somme tout exprimer avec les deux approches.

Pourquoi est-ce important? Principalement parce que cela rend votre application partie intégrante du Web - sa contribution à ce qui a fait du Web l'application d'Internet la plus populaire est directement proportionnelle au nombre de ressources qu'elle lui ajoute. Dans une approche RESTful, une application pourrait ajouter au Web quelque millions d'URIs représentant des clients; si elle est conçue comme les applications étaient conçues à l'époque de CORBA, sa contribution serait habituellement un point d'entrée unique - comparable à une toute petite porte donner accès à un univers de ressources seulement pour ceux qui en possèdent la clé.

L'interface uniforme permet aussi à tout composant qui comprend le protocole HTTP d'interagir avec votre application. Des exemples de composants qui bénéficient de cela peuvent inclure des clients génériques comme curl ou wget, les proxies, les caches, les serveurs HTTP, les passerelles, et même Google/Yahoo!/MSN et bien d'autres encore.

Pour résumer: Pour que des clients puissent interagir avec vos ressources, elles devraient implémenter le protocole applicatif par défaut (HTTP) correctement, i.e. utiliser les méthodes standard GET, PUT, POST et DELETE.

Avoir des représentations multiples pour les ressources

Nous avons ignoré une complication mineure jusqu'à présent: comment un client peut savoir comment traiter les donnnées qu'il reçoit, par exemple en résultant d'un GET ou d'un POST? L'approche prise par HTTP est d'autoriser la séparation du problème entre traiter la donnée et invoquer l'opération. En d'autres mots, un client qui sait interagir dans un format particulier peut interagir avec toutes les ressources capables de fournir une représentation dans ce format. Illustrons cela à l'aide d'un exemple. En utilisant la négociation de contenu d'HTTP, un client peut demander une représentation dans un format particulier:

GET /customers/1234 HTTP/1.1
Host: example.com 
Accept: application/vnd.mycompany.customer+xml  

Le résultat pourrait ainsi être un format XML propre à l'entreprise représentant une information clientèle. Si le client envoie une requête différente, par exemple comme celle-ci:

GET /customers/1234 HTTP/1.1
Host: example.com 
Accept: text/x-vcard 

Le résultat pourrait être l'adresse du client au format VCard. (Je n'ai pas montré les réponses, qui contiendraient des méta-données à propos du type de données dans l'en-tête HTTP Content-Type). Cela illustre pourquoi, idéalement, la représentation d'une ressource devrait être dans des formats standards - si un client connais à la fois le protocole HTTP et un ensemble de formats de données, il peut interagir avec n'importe quelle application RESTful dans le monde d'une manière utile. Malheureusement nous n'avons pas de format standard pour tout, mais vous pouvez facilement imaginer comment quelqu'un pourrait créer un écosystème plus petit à l'intérieur d'une entreprise ou entre des partenaires commerciaux en s'appuyant sur un format standardisé. Bien entendu cela s'applique dans les deux sens - un serveur qui est capable de consommer des données dans différent formats ne se préoccupe pas du type particulier de client avec qui il interagit, à conditions qu'ils suivent le protocole.

Il existe un autre avantage significatif à avoir des représentations multiples dans la pratique: si vous exposez à la fois une représentation HTML et XML de vos ressources, elles sont consommables non seulement par votre application mais aussi par n'importe quel navigateur Web standard - en d'autres mots l'information dans votre application devient disponible à tout utilisateur sachant naviguer sur le Web.

Il y a une autre manière d'exploiter ceci: vous pouvez transformer l'interface Web de votre application en son API - après tout, la conception d'API est souvent basé sur l'idée que tout ce qui est faisable dans l'interface graphique devrait l'être via l'API. Fusionner les deux tâches en une est une manière étonnament utile d'obtenir une meilleure interface Web à la fois pour les humains et les autres applications.

En résumé: Fournissez des représentations multiples des ressources pour couvrir différents besoins.

Communiquer sans état

Le dernier principe que je souhaite aborder est la communication sans état. En premier lieu, il est important d'insister sur le fait que, bien que REST englobe l'idée du sans-état, cela ne veux pas dire qu'une application ne peut pas avoir d'état - en fait, cela rendrait l'approche plutôt inutile dans la plupart des scénarios. REST impose que l'état soit soit transformé en état de ressource ou gardé côté client. En d'autres mots, un serveur ne devrait pas avoir à conserver une sorte de contexte de communication avec aucun des clients au delà de la durée d'une simple requête. La raison la plus évidente pour cela est la capacité à monter en charge - le nombre de clients interagissant avec le serveur impacterait significativement sa charge mémoire s'il devait conserver un état pour chaque client. (Notez que cela implique habituellement un peu de réorganisation, vous ne pouvez pas simplement coller une URI à des données de sessions et appeller ça du RESTful).

Mais il y a d'autres aspects qui pourraient être bien plus importants: la contrainte d'absence d'état isole le client des changements opérés sur le serveur puisqu'il n'a pas à parler au même serveur pendant deux requêtes consécutives. Un client pourrait recevoir un document contenant des liens du serveur, et pendant qu'il effectue un traitement quelconque le serveur pourrait être éteint, son disque dur arraché et remplacé, le logiciel mis à jour et redémarré - et si le client suit l'un des liens que le serveur lui a envoyé il ne s'en rendra même pas compte.

REST en théorie

J'ai une confession à faire: ce que j'ai expliqué n'est pas tout à fait REST, et je pourrais me faire incendier pour avoir un peu trop simplifié les choses. Mais je voulais introduire les choses d'une manière différente de d'habitude, donc je n'ai pas donné l'historique formel de REST au début de l'article. Laissez moi essayer de corriger cela, bien que de manière brève.

Avant tout, j'ai pris grand soin d'éviter de séparer REST d'HTTP lui-même et de l'utilisation d'HTTP de manière RESTful. Pour comprendre la relation entre ces différents aspects, nous devons jetter un oeil à l'histoire de REST.

Le terme REST a été défini par Roy T. Fielding dans sa thèse de Doctorat. Roy a été l'un des concepteurs principaux de nombreux protocoles essentiels du Web, dont HTTP et les URIs, et il a formalisé bon nombre d'idées en arrière plan dans ce document. (La thèse est considérée comme la "Bible de REST", à juste titre - après tout, l'auteur a inventé le terme, donc par définition tout ce qu'il a écris à son propos doit être considéré d'autorité). Dans la thèse, Roy commence par définir une méthodologie pour parler des styles architecturaux - des schémas abstraits, de haut-niveau, qui expriment les idées fondamentales derrière une approche architecturale. Chaque style architectural possède un ensemble de contraintes qui le définissent. Pour citer quelques exemples, le "style vide" ("null style", qui n'as pas du tout de contraintes), tuyaux et filtres ("pipes and filters"), client/serveur, objets distribués, et - vous l'avez deviné - REST.

Si tout ceci vous semble bien abstrait, vous avez raison - REST en-soi est un style de haut-niveau qui pourrait être implémenté en utilisant des technologies variées, et instancié en utilisant des valeurs variables pour ses propriétés abstraites. Par exemple, REST inclu les concepts de ressource et d'interface unifiée - i.e. l'idée que chaque ressource devrait répondre aux mêmes méthodes. Mais REST ne dicte pas quelles doivent être ces méthodes, ou combien il devrait y en avoir.

Une "incarnation" du style REST est HTTP (et un ensemble de standards associés, tels que les URIs), ou de manière légèrement plus abstraite l'architecture du Web lui-même. Pour continuer l'exemple précédent, HTTP "instançie" l'interface uniforme de REST avec une interface particulière: celle des verbes HTTP. Comme Fielding a défini le style REST après que le Web - ou du moins une grande partie du Web - ai été "terminé", on pourrait se poser la question de savoir s'il s'agit bien d'une correspondance à 100%. Mais en tout cas, le Web, HTTP et les URIs sont les seules instances majeures, très certainement les seules instances significatives du style REST dans son ensemble. Et comme Roy Fielding est à la fois l'auteur de la thèse sur REST et a eu une influence forte sur la conception de l'architecture du Web, cela n'est pas surprenant.

Enfin, j'ai utilisé le terme "HTTP RESTful" de temps en temps, pour une raison simple: beaucoup d'applications qui utilisent HTTP ne suivent pas les principes de REST - et de manière justifiable, on peut dire qu'utiliser HTTP sans suivre les principes REST équivaut à abuser d'HTTP. Bien sûr cela paraît un petit peu trop zélé - et de fait il y a souvent de bonnes raisons pour passer outre une des contraintes de REST, tout simplement parce que toute contrainte implique un compromis qui pourrait ne pas être acceptable dans une situation particulière. Mais souvent, les contraintes de REST sont violées simplement par manque de compréhension de leur utilité. Un exemple particulièrement retord: l'usage du GET HTTP pour invoquer des opérations telles que la suppression d'objets viole la contrainte de sécurité de REST et défie le bon sens (le client ne peut être tenu responsable, ce qui n'est probablement pas l'intention du développeur du serveur). Mais plus de détails (et d'autres abus notoires) dans un article prochain.

Résumé

Dans cet article j'ai essayé de fournir une introduction rapide aux concepts qui se cachent derrière REST, l'architecture du Web. Une approche HTTP RESTful pour exposer des fonctionnalités est différente de l'invocation de procédures à distance RPC, des Objets Distribués et des Web Services; cela demande un certain décalage de sa perception pour bien comprendre cette différence. Etre au courant des principes REST est bénéfique que vous soyez en train de construire seulement une interface Web ou que vous vouliez transformer l'API de votre application en un bon citoyen du Web.

A propos de l'auteur original

Stefan Tilkov est l'éditeur en chef de la communauté SOA d'InfoQ et co-fondateur, principal consultant et RESTafariste en chef de l'entreprise Germano-Suisse InnoQ.

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT