Pourquoi viser un design RESTful?
Les principes RESTful apportent les fonctionnalités qui rendent les sites Web faciles (pour un utilisateur humain aléatoire de les «surfer») à la conception de l'API des services Web , de sorte qu'ils sont faciles à utiliser pour un programmeur. REST n'est pas bon parce que c'est REST, c'est bon parce que c'est bon. Et c'est bien surtout parce que c'est simple .
La simplicité de HTTP simple (sans enveloppes SOAP et POSTservices surchargés à URI unique ), ce que certains peuvent appeler «manque de fonctionnalités» , est en fait sa plus grande force . Dès le départ, HTTP vous demande d' adressabilité et d' apatridie : les deux décisions de conception de base qui permettent à HTTP de rester évolutif jusqu'aux méga-sites (et méga-services) d'aujourd'hui.
Mais REST n'est pas la solution miracle: parfois un style RPC ("Remote Procedure Call" - comme SOAP) peut être approprié , et parfois d'autres besoins ont priorité sur les vertus du Web. C'est bon. Ce que nous n'aimons pas vraiment, c'est la complexité inutile . Trop souvent, un programmeur ou une entreprise fait appel à des services de style RPC pour un travail que l'ancien HTTP pourrait gérer très bien. L'effet est que HTTP est réduit à un protocole de transport pour une énorme charge XML qui explique ce qui se passe "vraiment" (pas l'URI ou la méthode HTTP en donnent un indice). Le service qui en résulte est beaucoup trop complexe, impossible à déboguer et ne fonctionnera pas à moins que vos clients n'aient la configuration exacte prévue par le développeur.
De la même manière qu'un code Java / C # ne peut pas être orienté objet, le simple fait d'utiliser HTTP ne rend pas une conception RESTful. On peut être pris dans la précipitation de penser à leurs services en termes d'actions et de méthodes à distance qu'il faudrait appeler. Pas étonnant que cela finisse principalement dans un service de style RPC (ou un hybride REST-RPC). La première étape consiste à penser différemment. Une conception RESTful peut être réalisée de plusieurs manières, l'une d'entre elles consiste à penser votre application en termes de ressources et non d'actions:
💡 Au lieu de penser en termes d'actions qu'il peut effectuer ("faire une recherche de lieux sur la carte") ...
... essayez de penser en termes de résultats de ces actions ("la liste des lieux sur la carte correspondant à un critère de recherche").
Je vais chercher des exemples ci-dessous. (Un autre aspect clé de REST est l'utilisation de HATEOAS - je ne le brosse pas ici, mais j'en parle rapidement dans un autre post .)
Problèmes de la première conception
Jetons un coup d'œil au design proposé:
ACTION http://api.animals.com/v1/dogs/1/
Tout d'abord, nous ne devrions pas envisager de créer un nouveau verbe HTTP ( ACTION). De manière générale, cela n'est pas souhaitable pour plusieurs raisons:
- (1) Étant donné uniquement l'URI du service, comment un programmeur "aléatoire" saura-t-il que le
ACTIONverbe existe?
- (2) si le programmeur sait qu'il existe, comment connaîtra-t-il sa sémantique? Que veut dire ce verbe?
- (3) quelles propriétés (sécurité, idempotence) doit-on s'attendre à ce que ce verbe ait?
- (4) Et si le programmeur a un client très simple qui ne gère que les verbes HTTP standard?
- (5) ...
Considérons maintenant l' utilisationPOST (je vais expliquer pourquoi ci-dessous, croyez-moi sur parole maintenant):
POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
{"action":"bark"}
Cela pourrait être OK ... mais seulement si :
{"action":"bark"}était un document; et
/v1/dogs/1/était un URI de "processeur de document" (semblable à une usine). Un «processeur de document» est un URI sur lequel vous «jetez des choses» et «oubliez» - le processeur peut vous rediriger vers une ressource nouvellement créée après le «lancement». Par exemple, l'URI pour publier des messages dans un service de courtier de messages, qui, après la publication, vous redirigera vers un URI qui montre l'état du traitement du message.
Je ne sais pas grand-chose de votre système, mais je parie déjà que les deux ne sont pas vrais:
{"action":"bark"} n'est pas un document , c'est en fait la méthode que vous essayez de vous faufiler ninja dans le service; et
- l'
/v1/dogs/1/URI représente une ressource "chien" (probablement le chien avec id==1) et non un processeur de document.
Donc, tout ce que nous savons maintenant, c'est que la conception ci-dessus n'est pas si RESTful, mais qu'est-ce que c'est exactement? Qu'y a-t-il de si mauvais? Fondamentalement, c'est mauvais parce que c'est un URI complexe avec des significations complexes. Vous ne pouvez rien en déduire. Comment un programmeur pourrait-il savoir qu'un chien a une barkaction qui peut être secrètement infusée avec un POST?
Concevoir les appels API de votre question
Alors allons droit au but et essayons de concevoir ces aboiements de manière REST en pensant en termes de ressources . Permettez-moi de citer le livre Restful Web Services :
Une POSTdemande est une tentative de création d'une nouvelle ressource à partir d'une ressource existante. La ressource existante peut être le parent de la nouvelle au sens de la structure de données, de la même manière que la racine d'un arbre est le parent de tous ses nœuds feuilles. Ou la ressource existante peut être une ressource "usine" spéciale
dont le seul but est de générer d'autres ressources. La représentation envoyée avec une POSTrequête décrit l'état initial de la nouvelle ressource. Comme avec PUT, une POSTdemande n'a pas du tout besoin d'inclure une représentation.
Suite à la description ci-dessus, nous pouvons voir que cela barkpeut être modélisé comme unedog sous -ressource de a (puisque a barkest contenu dans un chien, c'est-à-dire qu'un aboiement est "aboyé" par un chien).
De ce raisonnement, nous avons déjà obtenu:
- La méthode est
POST
- La ressource est
/barks, sous-ressource de chien:, /v1/dogs/1/barksreprésentant une bark"usine". Cet URI est unique pour chaque chien (car il est sous /v1/dogs/{id}).
Désormais, chaque cas de votre liste a un comportement spécifique.
1. Bark envoie juste un e-mail à dog.emailet n'enregistre rien.
Premièrement, aboyer (envoyer un e-mail) est-il une tâche synchrone ou asynchrone? Deuxièmement, la barkdemande nécessite-t-elle un document (l'e-mail, peut-être) ou est-il vide?
1.1 bark envoie un e-mail à dog.emailet n'enregistre rien (en tant que tâche synchrone)
Ce cas est simple. Un appel à la barksressource d'usine donne immédiatement un aboiement (un e-mail envoyé) et la réponse (si OK ou non) est donnée immédiatement:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(entity-body is empty - or, if you require a **document**, place it here)
200 OK
Comme il n'enregistre (ne change) rien, 200 OKc'est assez. Cela montre que tout s'est déroulé comme prévu.
1.2 bark envoie un e-mail à dog.emailet n'enregistre rien (en tant que tâche asynchrone)
Dans ce cas, le client doit avoir un moyen de suivre la barktâche. La barktâche doit alors être une ressource avec son propre URI:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
De cette façon, chacun barkest traçable. Le client peut alors émettre un GETà l' barkURI pour connaître son état actuel. Peut-être même utiliser un DELETEpour l'annuler.
2. bark envoie un e-mail à dog.email, puis incrémente dog.barkCountde 1
Celui-ci peut être plus délicat, si vous voulez informer le client que la dogressource est modifiée:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}
303 See Other
Location: http://api.animals.com/v1/dogs/1
Dans ce cas, l' locationintention de l'en- tête est de faire savoir au client qu'il doit jeter un coup d'œil dog. Depuis le RFC HTTP sur303 :
Cette méthode existe principalement pour permettre la sortie d'un
POSTscript activé pour rediriger l'agent utilisateur vers une ressource sélectionnée.
Si la tâche est asynchrone, une sous- barkressource est nécessaire tout comme la 1.2situation et 303doit être renvoyée en a GET .../barks/Ylorsque la tâche est terminée.
3. aboiement crée un nouveau " bark" enregistrement avec bark.timestampenregistrement lorsque l'écorce s'est produite. Il incrémente également dog.barkCountde 1.
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Ici, le barkest créé en raison de la demande, le statut 201 Createdest donc appliqué.
Si la création est asynchrone, un 202 Acceptedest requis ( comme le dit la RFC HTTP ) à la place.
L'horodatage enregistré fait partie de la barkressource et peut être récupéré avec un GETto. Le chien mis à jour peut également être "documenté" GET dogs/X/barks/Y.
4. bark exécute une commande système pour extraire la dernière version du code chien de Github. Il envoie ensuite un message texte pour dog.ownerleur dire que le nouveau code chien est en cours de production.
Le libellé de celui-ci est compliqué, mais c'est à peu près une tâche asynchrone simple:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Le client émettrait alors GETs pour /v1/dogs/1/barks/a65h44connaître l'état actuel (si le code était extrait, l'e-mail était envoyé au propriétaire et autre). Chaque fois que le chien change, un 303est applicable.
Emballer
Citant Roy Fielding :
La seule chose que REST exige des méthodes est qu'elles soient uniformément définies pour toutes les ressources (c'est-à-dire, afin que les intermédiaires n'aient pas à connaître le type de ressource pour comprendre la signification de la requête).
Dans les exemples ci-dessus, POSTest conçu de manière uniforme. Cela fera le chien " bark". Ce n'est ni sûr (ce qui veut dire que bark a des effets sur les ressources), ni idempotent (chaque requête en produit un nouveau bark), ce qui correspond bien au POSTverbe.
Un programmeur saurait: un POSTà barksdonne un bark. Les codes d'état de la réponse (également avec le corps de l'entité et les en-têtes si nécessaire) expliquent ce qui a changé et comment le client peut et doit procéder.
Remarque: Les principales sources utilisées étaient: le livre " Restful Web Services ", le HTTP RFC et le blog de Roy Fielding .
Éditer:
La question et donc la réponse ont beaucoup changé depuis leur création. La question initiale posée sur la conception d'un URI comme:
ACTION http://api.animals.com/v1/dogs/1/?action=bark
Vous trouverez ci-dessous l'explication des raisons pour lesquelles ce n'est pas un bon choix:
La manière dont les clients disent au serveur QUE FAIRE avec les données est l' information sur la méthode .
- Les services Web RESTful transmettent les informations de méthode dans la méthode HTTP.
- Les services de style RPC et SOAP typiques conservent les leurs dans le corps d'entité et l'en-tête HTTP.
SUR QUELLE PARTIE des données [le client veut que le serveur] opère est l' information de portée .
- Les services RESTful utilisent l'URI. Les services de style SOAP / RPC utilisent à nouveau le corps d'entité et les en-têtes HTTP.
À titre d'exemple, prenez l'URI de Google http://www.google.com/search?q=DOG. Là, les informations de méthode sont GETet les informations de portée sont /search?q=DOG.
Longue histoire courte:
- Dans les architectures RESTful , les informations de méthode vont dans la méthode HTTP.
- Dans les architectures orientées ressources , les informations de portée vont dans l'URI.
Et la règle d'or:
Si la méthode HTTP ne correspond pas aux informations de la méthode, le service n'est pas RESTful. Si les informations de portée ne sont pas dans l'URI, le service n'est pas orienté ressources.
Vous pouvez mettre l ' "action" "aboyer" dans l'URL (ou dans le corps de l'entité) et l'utiliser POST. Pas de problème là-bas, cela fonctionne et peut être le moyen le plus simple de le faire, mais ce n'est pas RESTful .
Pour garder votre service vraiment RESTful, vous devrez peut-être prendre du recul et réfléchir à ce que vous voulez vraiment faire ici (quels effets cela aura-t-il sur les ressources).
Je ne peux pas parler de vos besoins commerciaux spécifiques, mais laissez-moi vous donner un exemple: considérez un service de commande RESTful où les commandes sont à des URI comme example.com/order/123.
Maintenant, disons que nous voulons annuler une commande, comment allons-nous le faire? On peut être tenté de penser qu'il s'agit d'une «action» d' annulation et de la concevoir comme .POST example.com/order/123?do=cancel
Ce n'est pas RESTful, comme nous l'avons mentionné ci-dessus. Au lieu de cela, nous pourrions PUTune nouvelle représentation du orderavec un canceledélément envoyé à true:
PUT /order/123 HTTP/1.1
Content-Type: application/xml
<order id="123">
<customer id="89987">...</customer>
<canceled>true</canceled>
...
</order>
Et c'est tout. Si la commande ne peut pas être annulée, un code de statut spécifique peut être retourné. (Une conception de sous-ressource, comme POST /order/123/canceledavec le corps de l'entité truepeut, pour plus de simplicité, être également disponible.)
Dans votre scénario spécifique, vous pouvez essayer quelque chose de similaire. De cette façon, pendant qu'un chien aboie, par exemple, un GETat /v1/dogs/1/pourrait inclure cette information (par exemple <barking>true</barking>) . Ou ... si c'est trop compliqué, assouplissez votre exigence RESTful et tenez-vous-en POST.
Mettre à jour:
Je ne veux pas que la réponse soit trop grande, mais cela prend un certain temps pour maîtriser l'exposition d'un algorithme (une action ) en tant qu'ensemble de ressources. Au lieu de penser en termes d'actions ( "faire une recherche de lieux sur la carte" ), il faut penser en termes de résultats de cette action ( "la liste des lieux sur la carte correspondant à un critère de recherche" ).
Vous pouvez vous retrouver à revenir à cette étape si vous constatez que votre conception ne correspond pas à l'interface uniforme de HTTP.
Les variables de requête sont des informations de portée , mais ne dénotent pas de nouvelles ressources (il /post?lang=ens'agit clairement de la même ressource que /post?lang=jp, juste une représentation différente). Au contraire, ils sont utilisés pour transmettre l'état du client (comme ?page=10, pour que l'état ne soit pas conservé dans le serveur; ?lang=enest également un exemple ici) ou des paramètres d'entrée aux ressources algorithmiques ( /search?q=dogs, /dogs?code=1). Encore une fois, pas de ressources distinctes.
Propriétés des verbes HTTP (méthodes):
Un autre point clair qui apparaît ?action=somethingdans l'URI n'est pas RESTful, ce sont les propriétés des verbes HTTP:
GETet HEADsont sûrs (et idempotents);
PUTet DELETEsont idempotents seulement;
POST est ni.
Sécurité : Une demande GETou HEADest une demande de lecture de certaines données, pas une demande de modification d'un état de serveur. Le client peut faire une demande GETou HEAD10 fois et c'est la même chose qu'une seule fois, ou ne jamais le faire du tout .
Idempotence : Une opération idempotente en un qui a le même effet que vous l'appliquiez une ou plusieurs fois (en maths, multiplier par zéro est idempotent). Si vous avez DELETEune ressource une fois, la suppression à nouveau aura le même effet (la ressource est GONEdéjà).
POSTn'est ni sûr ni idempotent. Faire deux POSTdemandes identiques à une ressource «usine» entraînera probablement deux ressources subordonnées contenant les mêmes informations. Avec surchargé (méthode en URI ou entité-corps) POST, tous les paris sont ouverts.
Ces deux propriétés étaient importantes pour le succès du protocole HTTP (sur des réseaux peu fiables!): Combien de fois avez-vous mis à jour ( GET) la page sans attendre qu'elle soit complètement chargée?
Créer une action et la placer dans l'URL rompt clairement le contrat des méthodes HTTP. Encore une fois, la technologie vous permet, vous pouvez le faire, mais ce n'est pas une conception RESTful.