Une application Web en tant que client API REST: comment gérer les identificateurs de ressources

21

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' hrefattribut? 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:

  1. Le lien hypertexte hrefdoit conduire à l'application Web car il s'agit du système hébergeant l'interface utilisateur
  2. Le hrefdoit avoir un identifiant de l'entité, car l'application Web doit pouvoir s'adresser à l'entité lorsque la page cible s'ouvre
  3. 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 à 1234partir 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%2F1234sont 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:

  1. 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.
  2. 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.
  3. Stockez toutes les selfURL 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/678URL quelque part avec une nouvelle clé, disons 3, et que je génère l'URL de la page Web en tant que http://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?

Pavel Gatilov
la source
1
Ce que vous essayez d'accomplir n'est pas clair, probablement parce que votre intention simple est couverte sous les couches d'architecture que vous vous mettez les unes sur les autres, il est donc difficile de dire si les «API RESTful» vous aideraient vraiment. D'après ce que je comprends de votre problème, l'option 2 est une solution simple et réalisable. Le "problème" ici est inhérent aux "API RESTful". RestIsJustSqlReinvented et vous rencontrerez en effet le même problème lorsque vous essayez de récupérer un sous-graphe suffisamment complexe à partir de n'importe quel SGBDR. Utilisez un cache ou une représentation optimisée pour vos requêtes.
back2dos

Réponses:

5

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 à:

<Entity type="Pet">
    <Link rel="self" href="http://example.com/pets/1" />
    <Link rel="owner" href="http://example.com/people/1" />
    <UniqueName>Spot</UniqueName>
</Entity>

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:

http://example.com/GUI/pets/Spot

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:

http://example.com/GUI/owners/John/pets/1 (premier animal de compagnie dans la liste pour John)

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 à:

<Entity type="ResourceList">
    <Link rel="people" href="http://example.com/api/people" />
    <Link rel="pets" href="http://example.com/api/pets" />
</Entity>

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 à:

<Entity type="Person">
    <Link rel="self" href="http://example.com/api/people/1" />
    <Pets>
        <Link rel="pet" href="http://example.com/api/pets/1" />
        <Link rel="pet" href="http://example.com/api/pets/2" />
    </Pets>
    <UniqueName>John</UniqueName>
</Entity>
<Entity type="Person">
    <Link rel="self" href="http://example.com/api/people/2" />
    <Pets>
        <Link rel="pet" href="http://example.com/api/pets/3" />
    </Pets>
    <UniqueName>Jane</UniqueName>
</Entity>

L'interface graphique ferait une boucle à travers chaque ressource et imprimerait un élément de liste pour chaque personne utilisant le UniqueName comme "id":

<a href="http://example.com/gui/people/1">John</a>
<a href="http://example.com/gui/people/2">Jane</a>

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:

<Entity type="Pet">
    <Link rel="self" href="http://example.com/api/pets/1" />
    <Link rel="owner" href="http://example.com/api/people/1" />
    <UniqueName>Spot</UniqueName>
</Entity>

En utilisant cela, il peut imprimer un lien tel que:

<!-- Assumes that a pet can exist without an owner -->
<a href="http://example.com/gui/pets/Spot">Spot</a>

ou

<!-- Assumes that a pet MUST have an owner -->
<a href="http://example.com/gui/people/John/pets/Spot">Spot</a>

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:

  1. La page est ouverte et le Spot pour animaux de compagnie est demandé.
  2. Chargez la liste des ressources à partir du point d'entrée de l'API.
  3. Chargez la ressource associée au terme "animaux de compagnie".
  4. Parcourez chaque ressource de la réponse "Pets" et trouvez celle qui correspond à Spot.
  5. Affichez les informations pour le spot.

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.

Mike
la source
Merci, Mike. J'ai mis à jour ma question pour la rendre un peu plus claire. Le problème avec votre réponse est que je ne peux pas accepter qu'un client REST puisse analyser les URL. Si c'est le cas, il est couplé aux URL. Et cela viole l'une des idées fondamentales de REST: les clients doivent utiliser rels 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-ci relrestent les mêmes. L'analyse des URL nous rapproche davantage de SOAP que de REST.
Pavel Gatilov
Merci encore. Vous avez décrit l'approche que nous avons adoptée jusqu'à présent. D'une certaine manière, nous exposons les identifiants. La seule chose est que nous essayons d'exposer les identifiants naturels chaque fois que possible.
Pavel Gatilov
6

Cela signifie-t-il que les API RESTful ne peuvent pas servir de backends pour les applications Web?

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/...et http://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/1il 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.

Doug
la source
Je ne m'attends pas à ce que l'application Web soit simplement une représentation de l'API. Il peut y avoir beaucoup de différences, par exemple afficher plusieurs ressources enfants avec une racine sur une seule page. Je ne veux pas que les URL de l'application Web contiennent les identifiants internes du stockage de données de l'API, si vous voulez dire cela en disant que je m'attends à ce que les 2 systèmes soient les mêmes. Je ne suis pas concerné par la performance ici, ce n'est pas le problème. La question est en fait «Comment mettre 3 my.web.app/pets/3sans analyser les URL de l'API REST»?
Pavel Gatilov
Correction de ma propre reformulation: «Comment puis-je mettre 3 my.web.app/pets/3sans analyser l'URL de la ressource API REST correspondante my.rest.api/v0/persons/2/pets/3? Ou que dois-je y mettre?
Pavel Gatilov
Je pense que vous confondez l'état du client avec les représentations qui déterminent cet état. Vous ne mettez pas 3en app/pets/3cause app/pets/3est 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.
Doug
Pensez-y de cette façon - oubliez votre API et votre application. Supposons que vous vouliez créer un site qui permet aux gens de collecter leurs publications Facebook et Twitter préférées. Ce sont des systèmes distants. Vous n'essaierez pas de tunneler ou de modéliser les URI vers ces systèmes via le vôtre. Vous créeriez une ressource `` carte '' et ce serait votre serveur qui sait qui board/1pointe vers facebook.com/post/123et twitter.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.
Doug
Et donc, puisque vous voulez que votre API soit significativement différente de votre application (je pense toujours que cela est discutable) - la traiter comme un système distant ne devient pas différent de cela. Vous avez dit que la performance n'est pas le problème, mais vous avez également dit dans votre question que quelque chose comme ça "affecterait la performance".
Doug
5

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/personoù 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:

<Links>
    <Link ref="self" href="http://my.rest.api/api" />
    <Link rel="person" href="http://my.rest.api/api/person" />
    <Link rel="pet" href="http://my.rest.api/api/pet" />
</Links>

Puisque notre intention est d'afficher une liste de personnes, nous envoyons ensuite une GETdemande au href à partir du personlien href, qui peut renvoyer:

<Links>
    <Link ref="self" href="http://my.rest.api/api/person" />
    <Link rel="search" href="http://my.rest.api/api/person/search" />
</Links>

Nous voulons afficher les résultats de la recherche, nous utiliserons donc le service de recherche en envoyant une GETdemande au searchlien href, qui pourrait renvoyer:

<Persons>
    <Person>
        <Links>
            <Link rel="self" href="http://my.rest.api/api/person/1"/>
        </Links>
        <Pets>
            <Link rel="pet" href="http://my.rest.api/api/pet/10"/>
        </Pets>
    </Person>
    <Person>
        <Links>
            <Link rel="self" href="http://my.rest.api/api/person/2"/>
        </Links>
        <Pets>
            <Link rel="pet" href="http://my.rest.api/api/pet/20"/>
        </Pets>
    </Person>
</Persons>

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:

  • base d'API connue: http://my.rest.api/api
  • URL donnée pour l'entité individuelle: http://my.rest.api/api/person/1
  • identifiant unique: /person/1
  • notre URL de base: http://my.web.app
  • notre URL frontale générée: http://my.web.app/person/1

Nos résultats pourraient ressembler à:

<ul>
    <li><a href="http://my.web.app/person/1">A person</a></li>
    <li><a href="http://my.web.app/person/2">A person</a></li>
</ul>

Une fois qu'un utilisateur a suivi ce lien frontal vers la page de détails, à quelle URL envoyons-nous la GETdemande de détails sur ce détail person? Nous connaissons notre méthode de mappage des URL back-end dans les URL front-end, nous l'inversons donc simplement:

  • URL frontale: http://my.web.app/person/1
  • notre URL de base: http://my.web.app
  • identifiant unique: /person/1
  • base d'API connue: http://my.rest.api/api
  • URL de l'API générée: http://my.rest.api/api/person/1

Si l'API REST change de telle sorte qu'une personURL est maintenant http://my.rest.api/api/different-person-base/person/1et que quelqu'un avait déjà mis en signet http://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:

  • l'URL de base de l'API
  • la personrelation
  • la searchrelation

Je 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/1pourrait 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' selfURL 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/1exemple, nous nous retrouverions avec http://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/detailpour le routage et utiliserons le reste comme identifiant de chaîne opaque.


Je pense que cela introduit un couplage extrêmement serré de l'application Web avec l'API.

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.

Et si je voulais que la structure de l'URL de l'application Web diffère de celle de l'API?

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.

Que faire si les entités de mon application Web ne sont pas mappées aux entités API 1-1?

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.

Mike Partridge
la source
J'ai un problème avec cette approche. C'est en fait le numéro 1 de ma liste de solutions. Ce que je ne comprends pas, c'est: si l'application Web n'interprète pas les URL et traite les identifiants uniques comme des chaînes opaques (juste person/1, pet/3), alors comment savoir si un navigateur s'ouvre, http://my.rest.api/api/person/1il doit afficher l'interface utilisateur de la personne, et si elle s'ouvre http://my.rest.api/api/pet/3, puis animal UI?
Pavel Gatilov
Bonne question! J'ai mis à jour la réponse avec ma réponse.
Mike Partridge
Merci, Mike. Je pense que cela introduit un couplage extrêmement serré de l'application Web avec l'API. Et si je voulais que la structure de l'URL de l'application Web diffère de celle de l'API? Que faire si les entités de mon application Web ne sont pas mappées aux entités API 1-1? Je pense toujours que je ferais mieux d'adopter l'approche d'exposer certains identifiants, mais d'exhorter les clients à utiliser des liens pour la navigation.
Pavel Gatilov
C'est un sujet intéressant, donc j'espère ne rien manquer. J'ai mis à jour ma réponse avec les réponses à votre commentaire. Je pense que l'exposition de certains identifiants est un bon compromis entre RESTfulness complet et convivialité.
Mike Partridge
Ma principale préoccupation ici est un peu plus pratique. J'utilise ASP.NET MVC pour implémenter l'application Web, et en raison de certaines règles internes, je dois définir des modèles d'URL pris en charge par l'application. Autrement dit, si / a / {id} est défini, l'application gérera / a / 1, mais pas / a / 1 / b / 2. Cela conduit à l'obligation de recompiler l'application Web si les URL de l'API REST changent non seulement pour conserver les URL marquées d'un signet, mais aussi pour simplement faire fonctionner l'application Web lors de la navigation à partir de la racine. Tout simplement parce que les hyperliens intégrés aux pages html ne fonctionneront pas sans cela.
Pavel Gatilov
2

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.

Je ne devrais pas exposer les identifiants bruts de mes entités, mais plutôt renvoyer des hyperliens avec rel = "self"

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.

Cela signifie-t-il que les API RESTful ne peuvent pas servir de backends pour les applications Web?

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.

daramasala
la source
0

Je pense que si vous vous en tenez à quelque chose de similaire au Atom Syndication Formatvous ê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.

rusev
la source
Peut-être que je ne reçois pas quelque chose, mais il me semble que votre réponse n'explique pas comment générer les URL d'une application Web qui est cliente d'une API REST, n'est-ce pas?
Pavel Gatilov
0

Ne vous inquiétez pas des URL, ne vous inquiétez pas des types de médias.

Voir ici (troisième puce en particulier).

Une API REST doit consacrer presque tout son effort descriptif à définir le ou les types de média utilisés pour représenter les ressources et piloter l'état de l'application, ou à définir des noms de relation étendus et / ou un balisage hypertexte pour les types de média standard existants. .


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

          <a href="example.com/foo/123">click here</a>

correspond à quelque chose comme

          <link type="text/html" rel="self" href="example.com/foo/123">

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.

Rodrick Chapman
la source
Rodrick, ma question n'est pas sur la construction de l'API, mais plutôt sur la construction d'une application Web qui repose sur une API RESTful. Je peux à peine comprendre comment les types de médias peuvent m'aider à créer des URL pour l'application Web. Bien que les types de supports soient cruciaux pour le contrat de service et la découvrabilité.
Pavel Gatilov
@PavelGatilov - Le client de votre application Web est-il un être humain?
Rodrick Chapman
Oui, ça l'est. Et un très peu qualifié.
Pavel Gatilov
0

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.

Je pense que vous avez raison, c'est le moyen le plus simple. Vous pouvez relativiser les URL http://my.rest.api/apipour les rendre moins laides:

http://my.web.app/person/person%2F1234

Si l'URL fournie par l'API n'est pas relative à cette base, elle se dégrade sous la forme laide:

http://my.web.app/person/http%3A%2F%2Fother.api.host%2Fapi%2Fperson%2F1234

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:

http://my.web.app/person/1234 (best case)
http://my.web.app/http://other.api.host/api/person/1234 (ugly case)
ajlane
la source