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 POST
services 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
ACTION
verbe 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 bark
action 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 POST
demande 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 POST
requête décrit l'état initial de la nouvelle ressource. Comme avec PUT, une POST
demande n'a pas du tout besoin d'inclure une représentation.
Suite à la description ci-dessus, nous pouvons voir que cela bark
peut être modélisé comme unedog
sous -ressource de a (puisque a bark
est 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/barks
repré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.email
et n'enregistre rien.
Premièrement, aboyer (envoyer un e-mail) est-il une tâche synchrone ou asynchrone? Deuxièmement, la bark
demande nécessite-t-elle un document (l'e-mail, peut-être) ou est-il vide?
1.1 bark envoie un e-mail à dog.email
et n'enregistre rien (en tant que tâche synchrone)
Ce cas est simple. Un appel à la barks
ressource 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 OK
c'est assez. Cela montre que tout s'est déroulé comme prévu.
1.2 bark envoie un e-mail à dog.email
et n'enregistre rien (en tant que tâche asynchrone)
Dans ce cas, le client doit avoir un moyen de suivre la bark
tâche. La bark
tâ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 bark
est traçable. Le client peut alors émettre un GET
à l' bark
URI pour connaître son état actuel. Peut-être même utiliser un DELETE
pour l'annuler.
2. bark envoie un e-mail à dog.email
, puis incrémente dog.barkCount
de 1
Celui-ci peut être plus délicat, si vous voulez informer le client que la dog
ressource 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' location
intention 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
POST
script activé pour rediriger l'agent utilisateur vers une ressource sélectionnée.
Si la tâche est asynchrone, une sous- bark
ressource est nécessaire tout comme la 1.2
situation et 303
doit être renvoyée en a GET .../barks/Y
lorsque la tâche est terminée.
3. aboiement crée un nouveau " bark
" enregistrement avec bark.timestamp
enregistrement lorsque l'écorce s'est produite. Il incrémente également dog.barkCount
de 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 bark
est créé en raison de la demande, le statut 201 Created
est donc appliqué.
Si la création est asynchrone, un 202 Accepted
est requis ( comme le dit la RFC HTTP ) à la place.
L'horodatage enregistré fait partie de la bark
ressource et peut être récupéré avec un GET
to. 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.owner
leur 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 GET
s pour /v1/dogs/1/barks/a65h44
connaî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 303
est 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, POST
est 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 POST
verbe.
Un programmeur saurait: un POST
à barks
donne 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 GET
et 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 PUT
une nouvelle représentation du order
avec 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/canceled
avec le corps de l'entité true
peut, 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 GET
at /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=en
s'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=en
est é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=something
dans l'URI n'est pas RESTful, ce sont les propriétés des verbes HTTP:
GET
et HEAD
sont sûrs (et idempotents);
PUT
et DELETE
sont idempotents seulement;
POST
est ni.
Sécurité : Une demande GET
ou HEAD
est une demande de lecture de certaines données, pas une demande de modification d'un état de serveur. Le client peut faire une demande GET
ou HEAD
10 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 DELETE
une ressource une fois, la suppression à nouveau aura le même effet (la ressource est GONE
déjà).
POST
n'est ni sûr ni idempotent. Faire deux POST
demandes 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.