Plusieurs concepts liés au conflit REST dans ma tête lorsque j'essaye de l'implémenter.
J'ai un système d'API back-end REST-ful qui contient la logique métier et une application Web qui fournit l'interface utilisateur. À partir de diverses ressources sur REST (en particulier, REST en pratique: hypermédia et architecture de systèmes ), je sais que je ne devrais pas exposer les identificateurs bruts de mes entités, mais plutôt renvoyer des hyperliens avec rel="self"
.
Prenons l'exemple. L'API REST a une ressource qui renvoie une personne:
<Person>
<Links>
<Link rel="self" href="http://my.rest.api/api/person/1234"/>
</Links>
<Pets>
<Link rel="pet" href="http://my.rest.api/api/pet/678"/>
</Pets>
</Person>
Le problème se pose avec l'application Web. Supposons qu'il renvoie une page contenant un lien hypertexte vers les navigateurs:
<body class="person">
<p>
<a href="http://my.web.app/pet/???????" />
</p>
</body>
Que dois-je mettre dans l' href
attribut? Comment conserver l'URL de l'entité API dans l'application Web pour pouvoir obtenir l'entité lorsqu'un utilisateur ouvre la page cible?
Les exigences semblent contradictoires:
- Le lien hypertexte
href
doit conduire à l'application Web car il s'agit du système hébergeant l'interface utilisateur - Le
href
doit avoir un identifiant de l'entité, car l'application Web doit pouvoir s'adresser à l'entité lorsque la page cible s'ouvre - L'application Web ne doit pas analyser / construire d'URL REST car elle n'est pas conforme à REST, dit le livre mentionné
Les URI doivent être opaques pour les consommateurs. Seul l'émetteur de l'URI sait comment l'interpréter et le mapper à une ressource.
Donc, je ne peux pas simplement prendre à 1234
partir de l'URL de réponse de l'API, car en tant que client RESTful, je dois le traiter comme s'il s'agissait de quelque chose http://my.rest.api/api/AGRIDd~ryPQZ^$RjEL0j
. D'un autre côté, je dois donner une URL qui mène à mon application Web et suffit pour que l'application restaure en quelque sorte l'URL d'origine de l'API et utilise cette URL pour accéder aux ressources de l'API.
La manière la plus simple consiste probablement à utiliser les URL API des ressources comme identificateurs de chaîne. Mais les URL des pages Web comme http://my.web.app/person/http%3A%2F%2Fmy.rest.api%2Fapi%2Fperson%2F1234
sont laides.
Tout cela semble assez facile pour une application de bureau ou une application javascript à une seule page. Puisqu'ils vivent en continu, ils peuvent simplement conserver les URL en mémoire avec les objets de service pour la durée de vie de l'application et les utiliser si nécessaire.
Avec une application web, j'imagine plusieurs approches, mais toutes semblent étranges:
- Remplacez l'hôte dans les URL de l'API et conservez le résultat uniquement. L'énorme inconvénient est qu'il nécessite que l'application Web gère toute URL générée par l'API, ce qui signifie un couplage monstrueux. De plus, ce n'est plus RESTful, car mon application web commence à interpréter les URL.
- Exposez les ID bruts dans l'API REST avec les liens, utilisez-les pour créer les URL de Web App, puis utilisez les ID sur le serveur d'applications Web pour trouver les ressources requises dans l'API. C'est mieux, mais cela affectera les performances du serveur d'application Web car l'application Web devra passer par la navigation du service REST en émettant une chaîne de demandes get-by-id d'une certaine forme pour gérer toute demande provenant d'un navigateur. Pour une ressource quelque peu imbriquée, cela peut être coûteux.
- Stockez toutes les
self
URL renvoyées par l'API dans un mappage persistant (DB?) Sur le serveur d'applications Web. Générez des identifiants pour eux, utilisez-les pour créer les URL des pages d'applications Web et obtenir les URL des ressources du service REST. C'est-à-dire que je garde l'http://my.rest.api/pet/678
URL quelque part avec une nouvelle clé, disons3
, et que je génère l'URL de la page Web en tant quehttp://my.web.app/pet/3
. Cela ressemble à une implémentation de cache HTTP d'une certaine sorte. Je ne sais pas pourquoi, mais cela me semble bizarre.
Ou cela signifie-t-il que les API RESTful ne peuvent pas servir de backends pour les applications Web?
la source
Réponses:
Modifié pour répondre aux mises à jour des questions, suppression de la réponse précédente
En examinant vos modifications à votre question, je pense que je comprends un peu plus le problème auquel vous êtes confronté. Comme il n'y a pas de champ qui est un identifiant sur vos ressources (juste un lien), vous n'avez aucun moyen de faire référence à cette ressource spécifique dans votre interface graphique (c'est-à-dire un lien vers une page décrivant un animal spécifique).
La première chose à déterminer est de savoir si un animal de compagnie a un sens sans propriétaire. Si nous pouvons avoir un animal sans propriétaire, je dirais que nous avons besoin d'une sorte de propriété unique sur l'animal que nous pouvons utiliser pour s'y référer. Je ne crois pas que cela violerait de ne pas exposer l'ID directement car l'ID de ressource réel serait toujours caché dans un lien que le client REST ne serait pas en train d'analyser. Dans cet esprit, notre ressource pour animaux de compagnie peut ressembler à:
Nous pouvons maintenant mettre à jour le nom de cet animal de compagnie de Spot à Fido sans avoir à jouer avec les identifiants des ressources dans l'application. De même, nous pouvons faire référence à cet animal dans notre interface graphique avec quelque chose comme:
Si l'animal n'a aucun sens sans propriétaire (ou si les animaux ne sont pas autorisés dans le système sans propriétaire), nous pouvons utiliser le propriétaire dans le cadre de l '"identité" de l'animal dans le système:
Une petite note, si les animaux de compagnie et les personnes peuvent exister séparément, je ne ferais pas du point d'entrée de l'API la ressource "Personnes". Au lieu de cela, je créerais une ressource plus générique qui contiendrait un lien vers les personnes et les animaux domestiques. Il pourrait renvoyer une ressource qui ressemble à:
Donc, en ne connaissant que le premier point d'entrée dans l'API et en ne traitant aucune des URL pour comprendre les identificateurs système, nous pouvons faire quelque chose comme ceci:
L'utilisateur se connecte à l'application. Le client REST accède à la liste complète des ressources de personnes disponibles qui peuvent ressembler à:
L'interface graphique ferait une boucle à travers chaque ressource et imprimerait un élément de liste pour chaque personne utilisant le UniqueName comme "id":
En faisant cela, il pourrait également traiter chaque lien qu'il trouve avec une rel de "pet" et obtenir la ressource pour animal de compagnie telle que:
En utilisant cela, il peut imprimer un lien tel que:
ou
Si nous allons avec le premier lien et supposons que notre ressource d'entrée a un lien avec une relation de "pets", le flux de contrôle irait quelque chose comme ça dans l'interface graphique:
L'utilisation du deuxième lien serait une chaîne d'événements similaire, à l'exception du fait que People est le point d'entrée de l'API et nous obtiendrions d'abord une liste de toutes les personnes dans le système, trouver celle qui correspond, puis trouver tous les animaux de compagnie qui appartiennent à cette personne (en utilisant à nouveau la balise rel) et recherchez celle qui est nommée Spot afin que nous puissions afficher les informations spécifiques qui s'y rapportent.
la source
rel
s pour choisir les liens, mais ne doivent pas supposer aucune connaissance de la structure des URL. REST affirme qu'une API est libre de modifier les URL à volonté, à condition que celles-cirel
restent les mêmes. L'analyse des URL nous rapproche davantage de SOAP que de REST.Je mets en doute la pertinence de faire la différence entre une API REST et une application Web. Votre «application Web» devrait simplement être des représentations (HTML) alternatives des mêmes ressources - c'est-à-dire que je ne comprends pas comment ni pourquoi vous vous attendez à y accéder
http://my.rest.api/...
ethttp://my.web.app/...
qu'elles soient simultanément les mêmes et différentes.Votre "client" est le navigateur dans ce cas et il comprend HTML et JavaScript. C'est l'application Web à mon avis. Maintenant, vous pouvez être en désaccord et penser que vous accédez à ladite application Web en utilisant foo.com et exposez tout le reste via api.foo.com - mais vous devez alors demander, comment foo.com m'a-t-il fourni la représentation de la ressource? Le "back-end" de foo.com est parfaitement capable de comprendre comment découvrir les ressources de api.foo.com. Votre application Web est simplement devenue un proxy - pas différent de si vous parliez à une autre API (de quelqu'un d'autre) tous ensemble.
Ainsi, votre question peut être généralisée à: "Comment puis-je décrire les ressources en utilisant mes propres URI qui existent dans d'autres systèmes?" ce qui est trivial quand on considère que ce n'est pas le client (HTML / JavaScript) qui doit comprendre comment faire cela, mais le serveur. Si vous êtes d'accord avec mon premier défi, vous pouvez simplement penser à votre application Web comme une API REST distincte qui procède à un proxy ou délègue à une autre API REST.
Ainsi, lorsque votre client y accède,
my.web.app/pets/1
il sait présenter l'interface pour animaux de compagnie parce que c'est ce qui a été renvoyé par le modèle côté serveur, ou s'il s'agit d'une demande asynchrone pour une autre représentation (par exemple JSON ou XML), l'en-tête de type de contenu le dit .Le serveur qui fournit ceci est celui qui est chargé de comprendre ce qu'est un animal et comment découvrir un animal sur le système distant. La façon dont vous faites cela dépend de vous - vous pouvez simplement prendre l'ID et générer un autre URI, ce qui vous semble inapproprié, ou vous pouvez avoir votre propre base de données qui stocke l'URI distant et envoie la demande par procuration. Le stockage de cet URI est très bien - c'est équivalent au bookmarking. Vous feriez tout cela juste pour avoir un nom de domaine distinct. Honnêtement, je ne sais pas pourquoi vous le souhaitez - vos URI API REST doivent également pouvoir être mis en signet.
Vous avez déjà soulevé la plupart de ces questions dans votre question, mais je pense que vous les avez formulées d'une manière qui ne reconnaissait pas vraiment que c'est la façon pratique de faire ce que vous voulez faire (d'après ce que je ressens contrainte arbitraire - que l'API et l'application soient séparées). En demandant si les API REST ne peuvent pas être des back-ends pour les applications Web et en suggérant que les performances seraient un problème, je pense que vous vous concentrez sur les mauvaises choses. C'est comme dire que vous ne pouvez pas créer de Mashup. C'est comme dire que le Web ne fonctionne pas.
la source
my.web.app/pets/3
sans analyser les URL de l'API REST»?my.web.app/pets/3
sans analyser l'URL de la ressource API REST correspondantemy.rest.api/v0/persons/2/pets/3
? Ou que dois-je y mettre?3
enapp/pets/3
causeapp/pets/3
est opaque, il pointe sur l' ressource de votre application web veut. S'il s'agit d'une vue composée de plusieurs autres ressources (dans d'autres systèmes - votre API en fait partie), c'est à vous de stocker les liens hypertexte vers ces systèmes dans le serveur d'application Web, puis de les récupérer, de les résoudre dans leurs représentations ( JSON ou XML, par exemple), puis diffusez-les dans le cadre de votre réponse.board/1
pointe versfacebook.com/post/123
ettwitter.com/status/789
- lorsque vous allez fournir une représentation de votre carte, vous devez résoudre ces URI en une représentation avec laquelle vous pouvez travailler. Cachez là où vous en avez besoin.Préface
Cette réponse répond spécifiquement à la question de savoir comment gérer votre propre schéma d'URL, y compris les URL uniques pouvant être mises en signet pour les ressources pour lesquelles l'API REST principale n'expose pas explicitement un identifiant, et sans interpréter les URL fournies par l'API.
La découvrabilité nécessite une certaine quantité de connaissances, alors voici mon point de vue sur un scénario réel:
Disons que nous voulons une page de recherche
http://my.web.app/person
où les résultats incluent un lien vers la page de détails de chaque personne. La seule chose que notre code frontal doit savoir pour faire quoi que ce soit est l'URL de base pour sa source de données REST:http://my.rest.api/api
. La réponse à une demande GET à cette URL peut être:Puisque notre intention est d'afficher une liste de personnes, nous envoyons ensuite une
GET
demande au href à partir duperson
lien href, qui peut renvoyer:Nous voulons afficher les résultats de la recherche, nous utiliserons donc le service de recherche en envoyant une
GET
demande ausearch
lien href, qui pourrait renvoyer:Nous avons enfin nos résultats, mais comment construire nos URL frontales?
Supprimons la partie que nous savons avec certitude: l'URL de base de l'API et utilisons le reste comme identifiant frontal:
http://my.rest.api/api
http://my.rest.api/api/person/1
/person/1
http://my.web.app
http://my.web.app/person/1
Nos résultats pourraient ressembler à:
Une fois qu'un utilisateur a suivi ce lien frontal vers la page de détails, à quelle URL envoyons-nous la
GET
demande de détails sur ce détailperson
? Nous connaissons notre méthode de mappage des URL back-end dans les URL front-end, nous l'inversons donc simplement:http://my.web.app/person/1
http://my.web.app
/person/1
http://my.rest.api/api
http://my.rest.api/api/person/1
Si l'API REST change de telle sorte qu'une
person
URL est maintenanthttp://my.rest.api/api/different-person-base/person/1
et que quelqu'un avait déjà mis en signethttp://my.web.app/person/1
, l'API REST devrait (au moins pendant un certain temps) fournir une compatibilité descendante en répondant à l'ancienne URL avec une redirection vers la nouvelle. Tous les liens frontaux générés incluraient automatiquement la nouvelle structure.Comme vous l'avez probablement remarqué, il y a plusieurs choses que nous devons savoir pour naviguer dans l'API:
person
relationsearch
relationJe ne pense pas qu'il y ait quelque chose de mal à cela; nous ne supposons aucune structure d'URL spécifique à aucun moment, donc la structure de l'URL d'entité
http://my.rest.api/api/person/1
pourrait changer, et tant que l'API fournit une compatibilité descendante, notre code fonctionnera toujours.Vous avez demandé comment notre logique de routage pouvait faire la différence entre deux URL frontales:
http://my.rest.api/api/person/1
http://my.rest.api/api/pet/3
.Je soulignerai d'abord que vous avez utilisé la base d'API dans votre commentaire lorsque, dans notre exemple, nous utilisons des URL de base distinctes pour l'interface utilisateur et l'API REST. Je vais continuer l'exemple en utilisant des bases distinctes, mais partager une base n'est pas un problème. Nous pouvons (ou devrions être en mesure) de mapper les méthodes de routage de l'interface utilisateur en utilisant le type de support de l'en-tête Accept de la demande.
En ce qui concerne le routage vers une page de détail spécifique, nous ne pouvons pas différencier ces deux URL si nous nous efforçons d'éviter toute connaissance de la structure de l'
self
URL fournie par l'API (c'est-à-dire l'ID de chaîne opaque). Pour que cela fonctionne, incluons une autre de nos informations connues - le type d'entité avec lequel nous travaillons - dans nos URL frontales.Auparavant, nos URL frontales étaient au format:
${UI base}/${opaque string id}
Le nouveau format pourrait être:
${UI base}/${entity type}/${opaque string id}
Donc, en utilisant l'
/person/1
exemple, nous nous retrouverions avechttp://my.web.app/person/person/1
.Avec ce format, notre logique de routage d'interface utilisateur fonctionnerait avec
/person/person/1
, et sachant que le premier jeton de la chaîne a été inséré par nous-mêmes, nous pouvons le retirer et le router vers la page de détail appropriée (la personne, dans cet exemple) en fonction de celui-ci. Si vous vous sentez timide à propos de cette URL, nous pourrions donc en insérer un peu plus; peut être:http://my.web.app/person/detail/person/1
Dans ce cas, nous analyserons le
/person/detail
pour le routage et utiliserons le reste comme identifiant de chaîne opaque.Je suppose que vous voulez dire que, puisque notre URL frontale générée contient une partie de l'URL de l'API, si la structure de l'URL de l'API change sans prendre en charge l'ancienne structure, nous aurons besoin d'un changement de code afin de traduire l'URL en signet dans le nouvelle version de l'URL de l'API. En d'autres termes, si l'API REST modifie l'ID d'une ressource (la chaîne opaque), nous ne pouvons pas parler au serveur de cette ressource en utilisant l'ancien ID. Je ne pense pas que nous puissions éviter un changement de code dans cette situation.
Vous pouvez utiliser n'importe quelle structure d'URL que vous souhaitez. À la fin de la journée, une URL pouvant être mise en signet pour une ressource spécifique doit inclure quelque chose que vous pouvez utiliser pour obtenir une URL d'API qui identifie de manière unique cette ressource. Si vous générez votre propre identifiant et le cachez avec l'URL de l'API comme dans votre approche # 3, cela fonctionnera jusqu'à ce que quelqu'un essaie d'utiliser cette URL mise en signet après que cette entrée soit effacée du cache.
La réponse dépend de la relation. Dans les deux cas, vous auriez besoin d'un moyen de mapper le front-end aux URL d'API.
la source
person/1
,pet/3
), alors comment savoir si un navigateur s'ouvre,http://my.rest.api/api/person/1
il doit afficher l'interface utilisateur de la personne, et si elle s'ouvrehttp://my.rest.api/api/pet/3
, puis animal UI?Avouons-le, il n'y a pas de solution magique. Avez-vous lu le modèle de maturité Richardson ? Il divise la maturité de l'architecture REST en 3 niveaux: ressources, verbes HTTP et contrôles hypermédia.
Il s'agit de contrôles hypermédias. En avez-vous vraiment besoin? Cette approche présente de très bons avantages (vous pouvez les lire ici ). Mais les repas gratuits n'existent pas et vous devrez travailler dur (par exemple votre deuxième solution) si vous voulez les obtenir.
C'est une question d'équilibre - voulez-vous sacrifier les performances (et rendre votre code plus compliqué) mais avoir un système plus flexible? Ou préférez-vous garder les choses plus rapides et plus simples mais payer plus tard lorsque vous apportez des modifications à votre api / modèle?
En tant que personne ayant développé un système similaire (niveau logique métier, niveau Web et clients Web), j'ai choisi la deuxième option. Depuis que mon groupe a développé tous les niveaux, nous avons décidé qu'il était préférable d'avoir un peu de couplage (en informant le niveau Web des identifiants d'entité et de la création d'URL api) et en retour d'obtenir du code qui est plus simple. La rétrocompatibilité n'était pas non plus pertinente dans notre cas.
Si l'application Web a été développée par un tiers ou si la compatibilité descendante était un problème, nous aurions pu choisir différemment car il était alors très utile de pouvoir modifier la structure de l'URL sans changer l'application Web. De quoi justifier la complication du code.
Je pense que cela signifie que vous n'avez pas à créer une implémentation REST parfaite. Vous pouvez aller avec votre deuxième solution, ou exposer les identifiants d'entité ou peut - être passer des URL d'api . C'est OK tant que vous comprenez les implications et les compromis.
la source
Je pense que si vous vous en tenez à quelque chose de similaire au
Atom Syndication Format
vous êtes bon.Ici, les métadonnées décrivant l'entrée représentée peuvent être spécifiées en utilisant des éléments / attributs supplémentaires:
Selon [RFC4287] , contient un URI qui identifie de manière unique l'entrée
Selon [RFC4287] , cet élément est facultatif. S'il est inclus, il contient l'URI qu'un client doit utiliser pour récupérer l'entrée.
C'est juste mes deux cents.
la source
Ne vous inquiétez pas des URL, ne vous inquiétez pas des types de médias.
Voir ici (troisième puce en particulier).
Dans le cas d'une application Web typique, le client est un humain ; le navigateur n'est qu'un agent .
Donc, une balise d'ancrage comme
correspond à quelque chose comme
L'URL est toujours opaque pour l'utilisateur, tout ce qui l'intéresse, ce sont les types de médias (par exemple
text/html, application/pdf, application/flv, video/x-flv, image/jpeg, image/funny-cat-picture etc
). Le texte descriptif contenu dans l'ancre (et dans l'attribut title) n'est qu'un moyen d'étendre le type de relation de manière intelligible pour l'homme.La raison pour laquelle vous souhaitez que l'URI soit opaque pour les clients est de réduire le couplage (l'un des principaux objectifs de REST). Le serveur peut modifier / réorganiser les URI sans affecter le client (tant que vous avez une bonne stratégie de mise en cache - ce qui peut signifier aucune mise en cache du tout).
En résumé
Assurez-vous simplement que le client (humain ou machine) se soucie des types de médias et des relations plutôt que des URL et tout ira bien.
la source
Je pense que vous avez raison, c'est le moyen le plus simple. Vous pouvez relativiser les URL
http://my.rest.api/api
pour les rendre moins laides:Si l'URL fournie par l'API n'est pas relative à cette base, elle se dégrade sous la forme laide:
Pour aller plus loin, inspectez la réponse du serveur API pour déterminer le type de vue que vous souhaitez présenter et arrêtez de coder le délimiteur de segment de chemin et les deux-points:
la source