J'ai trois questions sur la conception de l'API REST sur lesquelles j'espère que quelqu'un pourra faire la lumière. J'ai cherché sans relâche pendant de nombreuses heures, mais je n'ai trouvé de réponses à mes questions nulle part (peut-être que je ne sais pas quoi chercher?).
question 1
Ma première question concerne les actions / RPC. Je développe une API REST depuis un certain temps et j'ai l'habitude de penser aux choses en termes de collections et de ressources. Cependant, je suis tombé sur quelques cas où le paradigme ne semble pas s'appliquer et je me demande s'il existe un moyen de concilier cela avec le paradigme REST.
Plus précisément, j'ai un cas où la modification d'une ressource entraîne la génération d'un e-mail. Cependant, à un stade ultérieur, l'utilisateur peut spécifiquement indiquer qu'il souhaite renvoyer l'e-mail envoyé précédemment. Lors du renvoi de l'e-mail, aucune ressource n'est modifiée. Aucun état n'est changé. C'est simplement une action qui doit se produire. L'action est liée au type de ressource spécifique.
Est-il approprié de mélanger une sorte d'appel à l'action avec un URI de ressource (par exemple /collection/123?action=resendEmail
)? Serait-il préférable de spécifier l'action et de lui transmettre l'identifiant de la ressource (par exemple /collection/resendEmail?id=123
)? Est-ce la mauvaise façon de procéder? Traditionnellement (au moins avec HTTP), l'action en cours est la méthode de requête (GET, POST, PUT, DELETE), mais celles-ci ne permettent pas vraiment des actions personnalisées avec une ressource.
question 2
J'utilise la partie chaîne de requête de l'URL pour filtrer l'ensemble des ressources retournées lors de l'interrogation d'une collection (par exemple /collection?someField=someval
). Dans mon contrôleur API, je détermine ensuite quel type de comparaison il va faire avec ce champ et cette valeur. J'ai trouvé que ça ne marche vraiment pas. J'ai besoin d'un moyen pour permettre à l'utilisateur de l'API de spécifier le type de comparaison qu'il souhaite effectuer.
La meilleure idée que j'ai eue jusqu'à présent est de permettre à l'utilisateur de l'API de le spécifier comme un appendice au nom du champ (par exemple /collection?someField:gte=someval
- pour indiquer qu'il doit renvoyer des ressources où someField
est supérieur ou égal à (> =) ce qui someval
est . Est-ce une bonne idée? Une mauvaise idée? Si oui, pourquoi? Y a-t-il une meilleure façon de permettre à l'utilisateur de spécifier le type de comparaison à effectuer avec le champ et la valeur donnés?
question 3
Je vois souvent des URI qui ressemblent à quelque chose /person/123/dogs
pour obtenir le person
s dogs
. J'ai généralement évité quelque chose comme ça parce qu'en fin de compte, je pense qu'en créant un URI comme celui-ci, vous accédez simplement à une dogs
collection filtrée par un person
ID spécifique . Ce serait équivalent à /dogs?person=123
. Y a-t-il vraiment une bonne raison pour qu'un URI REST ait plus de deux niveaux de profondeur ( /collection/resource_id
)?
Réponses:
Je préfère modéliser cela d'une manière différente, avec une collection de ressources représentant les courriels qui doivent être envoyés; l'envoi sera traité par les internes du service en temps voulu, auquel cas la ressource correspondante sera supprimée. (Ou l'utilisateur pourrait SUPPRIMER la ressource plus tôt, provoquant l'annulation de la demande pour l'envoi.)
Quoi que vous fassiez, ne mettez pas de verbes dans le nom de la ressource! C'est le nom (et la partie requête est l'ensemble d'adjectifs). Verbes nominatifs bizarres REST!
Je préfère spécifier une clause de filtre générale et l'avoir comme paramètre de requête facultatif sur toute demande de récupération du contenu de la collection. Le client peut alors spécifier exactement comment restreindre l'ensemble retourné, de la manière que vous désirez. Je m'inquiéterais également un peu de la découvrabilité du langage de filtre / requête; plus vous le faites riche, plus il est difficile à découvrir pour des clients arbitraires. Une approche alternative qui, au moins théoriquement, traite de ce problème de découverte est de permettre de créer des sous-ressources de restriction de la collection, que les clients obtiennent en POSTANT un document décrivant la restriction à la ressource de collection. C'est toujours un léger abus, mais au moins c'est un que vous pouvez clairement rendre découvrable!
Ce type de découverte est l'une des choses que je trouve le moins forte avec REST.
Lorsque la collection imbriquée est vraiment une sous-fonctionnalité des entités membres de la collection externe, il est raisonnable de les structurer en tant que sous-ressource. Par «sous-fonctionnalité», je veux dire quelque chose comme la relation de composition UML, où détruire la ressource externe signifie naturellement détruire la collection interne.
D'autres types de collection peuvent être modélisés comme une redirection HTTP; on
/person/123/dogs
peut donc en effet y répondre en faisant un 307 qui redirige vers/dogs?person=123
. Dans ce cas, la collection n'est pas réellement une composition UML, mais plutôt une agrégation UML. La différence compte; c'est significatif!la source
resendEmail
action puisse être gérée en créant une collection et en la publiant, cela semble moins naturel. En fait, je ne stocke rien dans la base de données lorsqu'un e-mail est renvoyé (pas besoin). Aucune ressource n'est modifiée, il s'agit donc simplement d'une action qui réussit ou échoue. Je n'ai pas pu retourner un ID de ressource qui existe au-delà de la durée de vie de l'appel, ce qui fait qu'une telle implémentation est un hack au lieu d'être RESTful. Ce n'est tout simplement pas une opération CRUD.Il est compréhensible d'être un peu confus sur la façon d'utiliser correctement REST en fonction de toutes les façons dont j'ai vu de grandes entreprises concevoir leurs API REST.
Vous avez raison en ce que REST est un système de collecte de ressources. Cela signifie Représentational State Transfer. Pas une bonne définition si vous me demandez. Mais les principaux concepts sont les 4 VERBES HTTP et être sans état.
La chose importante à noter est que vous n'avez que 4 VERBES avec REST. Ce sont GET, POST, PUT et DELETE. Votre
resend
exemple serait d'ajouter un nouveau verbe à REST. Cela devrait être un drapeau rouge.question 1
Il est important de réaliser que l'appelant de votre API REST ne devrait pas avoir à savoir que l'exécution d'un
PUT
sur votre collection entraînerait la génération d'un e-mail. Ça sent une fuite pour moi. Ce qu'ils pouvaient savoir, c'est que l'exécution d'unePUT
tâche pouvait entraîner des tâches supplémentaires qu'ils pourraient interroger plus tard. Ils le sauraient en effectuant uneGET
sur la ressource récemment créée. CelaGET
retournerait la ressource et tous lesTask
identifiants de ressource qui lui sont associés. Vous pouvez ensuite interroger ces tâches plus tard pour déterminer leur statut et même en soumettre une nouvelleTask
.Vous avez quelques options.
REST - Approche basée sur les ressources de tâches
Créez une
tasks
ressource dans laquelle vous pouvez soumettre des tâches spécifiques dans votre système pour effectuer des actions. Vous pouvez ensuite effectuerGET
la tâche en fonction de la valeurID
renvoyée pour déterminer son état.Ou vous pouvez mélanger dans un
SOAP over HTTP
service Web afin d'ajouter du RPC à votre architecture.interroger toutes les tâches pour une ressource spécifique
GET http://server/api/myCollection/123/tasks
exemple de ressource de tâche
PUT http://server/api/tasks
==> renvoie l'id de la tâche
223334
GET http://server/api/tasks/223334
REST - Utilisation de POST pour déclencher des actions
Vous pouvez toujours
POST
ajouter des données à une ressource. À mon avis, cela violerait l'esprit de REST, mais ce serait toujours conforme.Vous pouvez faire un POST similaire à ceci:
POST http://server/api/collection/123
{ "action" : "send-email" }
Vous mettrez à jour la ressource 123 de la collection avec des données supplémentaires. Ces données supplémentaires sont essentiellement une action demandant au backend d'envoyer un e-mail pour cette ressource.
Le problème que j'ai avec ceci est qu'un
GET
sur la ressource retournera ces données mises à jour. Cependant, cela résoudrait vos besoins tout en étant RESTful.SOAP - Service Web qui accepte les ressources obtenues à partir de REST
Créez un nouveau WebService dans lequel vous pouvez envoyer des e-mails en fonction de l'ID de ressource précédent à partir de l'API REST. Je n'entrerai pas dans les détails de SOAP ici car la question d'origine concerne REST et ces deux concepts / technologies ne doivent pas être comparés car ce sont des pommes et des oranges .
question 2
Vous avez également quelques options ici:
Il semble que de nombreuses grandes entreprises qui publient des API REST exposent une
search
collection qui n'est vraiment qu'un moyen de transmettre des paramètres de requête pour renvoyer des ressources.GET http://server/api/search?q="type = myCollection & someField >= someval"
Qui retournerait une collection de ressources REST pleinement qualifiées telles que:
Ou vous pouvez autoriser quelque chose comme MVEL comme paramètre de requête.
question 3
Je préfère les sous-niveaux que d'avoir à remonter et interroger l'autre ressource avec un paramètre de requête. Je ne pense pas qu'il existe une règle d'une manière ou d'une autre. Vous pouvez implémenter les deux façons et permettre à l'appelant de décider lequel est le plus approprié en fonction de la façon dont il est entré pour la première fois dans le système.
Remarques
Je ne suis pas d'accord sur les commentaires de lisibilité des autres. Malgré ce que certains pourraient penser, REST n'est toujours pas destiné à la consommation humaine. C'est pour la consommation de la machine. Si je veux voir mes Tweets, j'utilise le site Web régulier de Twitters. Je n'effectue pas de REST GET avec leur API. Si je veux faire quelque chose par programme avec mes tweets, j'utilise leur API REST. Oui, les API doivent être compréhensibles, mais ce
gte
n'est pas si mal, ce n'est tout simplement pas intuitif.L'autre chose principale avec REST est que vous devriez pouvoir commencer à tout moment donné dans votre API et naviguer vers toutes les autres ressources associées SANS connaître à l'avance l'URL exacte des autres ressources. Les résultats du
GET
VERBE dans REST doivent renvoyer l'URL REST complète des ressources qu'il référence. Ainsi, au lieu d'une requête renvoyant l'ID d'unPerson
objet, il retournerait l'URL entièrement qualifiée telle quehttp://server/api/people/13
. Ensuite, vous pouvez toujours parcourir par programme les résultats même si l'URL a changé.Réponse au commentaire
Dans le monde réel, il y a en fait des choses qui doivent se produire qui ne créent, lisent, mettent à jour ou suppriment (CRUD) une ressource.
Des actions supplémentaires peuvent être prises sur les ressources. Les bases de données relationnelles typiques prennent en charge le concept de procédures stockées. Ce sont des commandes supplémentaires qui peuvent être exécutées sur un ensemble de données. REST n'a pas intrinsèquement ce concept. Et il n'y a aucune raison que ce soit le cas. Ces types d'actions sont parfaits pour les services Web RPC ou SOAP.
C'est le problème général que je vois avec les API REST. Les développeurs n'aiment pas les limitations conceptuelles qui entourent REST, ils l'adaptent donc pour faire ce qu'ils veulent. Cela le rompt cependant d'être un service RESTful. Essentiellement, ces URL deviennent des
GET
appels sur des servlets de type pseudo-REST.Vous avez quelques options:
POST
en charge de données supplémentaires sur la ressource pour effectuer une action.Si vous avez utilisé un paramètre de requête quel VERBE HTTP utiliseriez-vous pour renvoyer l'e-mail?
GET
- Est-ce que cela renvoie l'e-mail ET renvoie les données de la ressource? Et si un système mettait cette URL en cache et la traitait comme l'URL unique de cette ressource. Chaque fois qu'ils frappaient l'URL, il renvoyait un e-mail.POST
- Vous n'avez pas réellement envoyé de nouvelles données à la ressource, juste un paramètre de requête supplémentaire.Sur la base de toutes les exigences données, faire un
POST
sur la ressource avec desaction field
données POST résoudra le problème.la source
Question 1: Est-il approprié de mélanger une sorte d'appel d'action avec un URI de ressource [ou] serait-il préférable de spécifier l'action et de lui transmettre l'ID de ressource?
Bonne question. Dans ce cas, je vous conseille d'utiliser cette dernière approche, à savoir spécifier l'action et lui transmettre un ID de ressource. De cette façon, lorsque votre ressource est modifiée pour la première fois, elle appelle à son tour l'
/sendEmail
action (note latérale: pas besoin de l'appeler "renvoyer") en tant que demande RESTful distincte (que vous pourrez ensuite appeler encore et encore, indépendamment de la ressource en cours de modification ).Question 2: concernant l'utilisation d'un opérateur de comparaison comme ceci:
/collection?someField:gte=someval
Bien que cela soit techniquement correct, c'est probablement une mauvaise idée. L'un des principes clés de REST est la lisibilité. Je vous suggère de simplement passer l'opérateur de comparaison comme un autre paramètre, par exemple:
/collection?someField=someval&operator=gte
et bien sûr de concevoir votre API de sorte qu'il réponde à un cas par défaut (dans le cas où leoperator
paramètre est laissé hors de l'URI).Question 3: Existe - t-il vraiment une bonne raison pour qu'un URI REST ait plus de deux niveaux de profondeur?
Ouaip; pour l'abstraction. J'ai vu quelques API REST qui utilisent des couches d'abstraction à travers plusieurs niveaux d'URI, par exemple:
/vehicles/cars/123
ou/vehicles/bikes/123
qui à leur tour vous permettent de travailler avec des informations utiles concernant les deux/vehicles
et les/vehicles/bikes
collections. Cela dit, je ne suis pas un grand fan de cette approche; vous aurez rarement besoin de le faire dans la pratique, et il est probable que vous puissiez repenser l'API pour utiliser seulement 2 niveaux.Et oui, comme le suggèrent les commentaires ci-dessus, à l'avenir, il serait préférable de diviser vos questions en messages séparés;)
la source
/collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq
.Pour la question 2, une alternative différente peut être plus flexible: considérez chaque recherche comme une ressource que l'utilisateur crée avant d'utiliser.
disons que vous avez un conteneur "recherches", là vous faites un
POST /api/searches/
avec la spécification de requête sur le contenu. il peut s'agir d'un document JSON, XML ou même d'un document SQL, tout ce qui est plus facile pour vous. Si la requête est correctement analysée, une nouvelle recherche est créée en tant que nouvelle ressource avec son propre URI, disons/api/searches/q123/
Ensuite, le client peut simplement
GET /api/searches/q123/
récupérer les résultats de la requête.Enfin, vous pouvez soit demander au client de supprimer la requête, soit la purger après la fermeture de la session.
la source
Non, ce n'est pas approprié, car les IRI servent à identifier les ressources et non les opérations (cependant ppl utilise cette méthode de substitution de méthode pendant un certain temps, dans les cas où l'utilisation de méthodes non POST et GET n'est pas prise en charge). Vous pouvez rechercher une méthode HTTP appropriée ou en créer une nouvelle. POST peut être votre ami dans ces cas (veuillez l'utiliser s'il ne trouve pas de méthode appropriée et que la demande n'est pas récupérée). Une autre approche pour faire des ressources à partir de l'envoi d'e-mails et
POST /emails
peut donc envoyer des e-mails sans créer de véritable ressource. Btw. La structure d'URI ne porte pas de sémantique, donc d'un point de vue REST peu importe le type d'URI que vous utilisez. Ce qui compte, ce sont les métadonnées (par exemple , la relation de lien ) attribuées aux liens que vous avez envoyés aux clients.Vous n'avez pas à créer votre propre langage de requête. Je préfère utiliser une version déjà existante et ajouter une description de la requête aux métadonnées du lien. Vous devriez probablement utiliser un type de média RDF (par exemple JSON-LD) pour cela ou utiliser un type MIME personnalisé (afaik il n'y a pas de format non-RDF qui le supporte). Utiliser les normes existantes découplent votre client du serveur, c'est à cela que sert la contrainte d'interface uniforme.
Comme je l'ai mentionné précédemment, la structure URI n'a pas d'importance du point de vue REST. Vous pouvez utiliser
/x71fd823df2
par exemple. Cela aurait toujours du sens pour les clients car ils vérifient les métadonnées affectées aux liens et non la structure URI. L'URI a pour principal objectif d'identifier les ressources. Dans la norme URI, ils indiquent que le chemin contient des données hiérarchiques et que la requête contient des données non hiérarchiques. Mais il peut être très subjectif ce qui est hiérarchique. C'est pourquoi vous rencontrez des URI profonds à plusieurs niveaux et des URI avec de longues requêtes.Vous devriez lire au moins les contraintes REST de la dissertation Fielding , la norme HTTP et probablement les API Web de 3e génération de Markus.
la source