Une API REST peut-elle renvoyer plusieurs ressources en une seule ressource composée?

10

Je suis en train de créer une API REST et actuellement, je rencontre le problème suivant:

  • Fooest la première ressource. Les opérations CRUD peuvent être appliquées via l' /foo/URI.
  • Barest la deuxième ressource. Les opérations CRUD peuvent être appliquées via l' /bar/URI.
  • Tout Fooest associé à zéro ou un Bar. La raison pour laquelle je ne traite pas Barcomme une sous-ressource de Fooest parce que la même Barinstance peut être partagée entre plusieurs mutuels Foo. J'ai donc pensé qu'il valait mieux y accéder via un URI indépendant au lieu de /foo/[id]/bar.

Mon problème est que dans un nombre important de cas, les clients qui demandent une Fooinstance sont également intéressés par l' Barinstance associée . Actuellement, cela signifie qu'ils doivent effectuer deux requêtes au lieu d'une. Je veux présenter un moyen qui permet d'obtenir les deux objets avec une seule requête, mais je ne sais pas comment modéliser l'API pour le faire. Ce que j'ai trouvé jusqu'à présent:

  • Je pourrais introduire un paramètre de requête similaire à ceci: /foo/[id]?include_bar=true. Le problème avec cette approche est que la représentation des ressources (par exemple la structure JSON) de la réponse devrait avoir un aspect différent (par exemple, un conteneur tel qu'au { foo: ..., bar: ... }lieu d'être juste un sérialisé Foo), ce qui rend le Foopoint de terminaison de la ressource "hétérogène". Je ne pense pas que ce soit une bonne chose. Lors de la requête /foo, les clients doivent toujours obtenir la même représentation des ressources (structure), quels que soient les paramètres de requête.
  • Une autre idée consiste à introduire un nouveau point de terminaison en lecture seule, par exemple /fooandbar/[foo-id]. Dans ce cas, ce n'est pas un problème de retourner une représentation comme { foo: ..., bar: ... }, car alors c'est juste la représentation "officielle" de la fooandbarressource. Cependant, je ne sais pas si un tel point de terminaison d'aide est vraiment RESTful (c'est pourquoi j'ai écrit "can" dans le titre de la question. Bien sûr, c'est techniquement possible, mais je ne sais pas si c'est une bonne idée).

Qu'est-ce que tu penses? Y a-t-il d'autres possibilités?

ceran
la source
Quel est le terme pour la relation entre Foo et Bar? Pourriez-vous dire que Bar est un parent de Foo?
Nathan Merrill
Un Barne peut exister sans être associé à un Foo. Cependant, comme je l'ai écrit ci-dessus, il est possible que plusieurs Foos partagent la même chose Bar. Il devrait être possible de créer un Foosans Barassocié, donc je ne pense pas que cela Bardevrait être traité comme parent.
ceran
1
Je pense que vous rencontrez certains des problèmes que j'ai rencontrés en traduisant directement les relations de modèle de domaine en URI et en assimilant les ressources aux entités de domaine . Cela peut intéresser les API REST doivent être basées sur l'hypertexte . Attention particulière au 4ème point
Laiv

Réponses:

6

Une API REST de niveau 3 vous renverrait un Fooet également un lien indiquant le lien Bar.

GET /foo/123
<foo id="123">
  ..foo stuff..
  <link rel="bar" uri="/bar/456"/>
</foo>

Vous pouvez ensuite ajouter une fonctionnalité "drill down" à votre API qui permet la navigation des liens;

GET /foo/123?drilldown=bar
<foo id="123">
  ..foo stuff..
  <link rel="bar" uri="/bar/456">
    <bar id="456">
      ..bar stuff...
    </bar>
  </link>
</foo>

La fonction d'exploration descendante se situerait devant les API et intercepterait les réponses. Il effectuerait les appels vers le bas et remplirait les détails avant de remettre la réponse à l'appelant.

C'est une chose assez courante dans le REST de niveau 3 car cela réduit considérablement la conversation client / serveur sur http lent. L'entreprise pour laquelle je travaille produit une API REST de niveau 3 avec exactement cette fonctionnalité.

Mise à jour: pour ce que cela vaut, voici à quoi cela pourrait ressembler dans JSON. C'est ainsi que notre API le structurerait. Notez que vous pouvez imbriquer vos explorations pour extraire des liens de liens, etc.

GET /foo/123?drilldown=bar

{
  "self": {
    "type": "thing.foo",
    "uri": "/foo/123=?drilldown=bar",
    "href": "http://localhost/api/foo/123?drilldown=bar"
  },
  "links": [
    {
      "rel": "bar",
      "rev": "foo",
      "type": "thing.bar",
      "uri": "/bar/456",
      "href": "http://localhost/api/bar/456"
    }
  ],
  "_bar": [
    {
      "self": {
        "type": "thing.bar",
        "uri": "/bar/456",
        "href": "http://localhost/api/bar/456"
      },
      "links": [
        {
          ..other link..
        },
        {
          ..other link..
        }
      ]
    }
  ]
}
Qwerky
la source
Intéressant, j'utilise déjà des liens / contrôles hypermédias pour supprimer le couplage étroit aux URI, mais je n'ai pas pensé à l'idée "drill down" qui semble très prometteuse. À quoi pourrait ressembler une représentation JSON? Pour le moment, chaque représentation JSON de mes ressources contient un linkstableau, chaque entrée est un objet lien avec un relet un urichamp (similaire à votre exemple xml). Dois-je simplement ajouter un troisième champ à chaque objet lien (par exemple data)? Existe-t-il une norme?
ceran
Le drill down n'est pas vraiment une fonction de repos, donc il n'y a pas de normes (du moins à ma connaissance).
Qwerky
Il existe certaines normes proposées, telles que stateless.co/hal_specification.html que j'utilise dans nos applications. C'est très proche de votre exemple.
Pete Kirkham
4

Si 95% de toutes les requêtes veulent Fooainsi que Bar, puis il suffit de retourner à l' intérieur de l' Fooobjet lorsque vous demandez un Foo. Ajoutez simplement une propriété bar(ou un autre terme pour la relation) et placez-y l' Barobjet. Si la relation n'existe pas, utilisez null.

Je pense que vous y pensez trop :)

Nathan Merrill
la source
Je n'aurais pas dû trouver ce chiffre (95%), c'était une erreur, désolé. Ce que je voulais dire, c'est qu'une grande partie des demandes s'intéresse aux deux ressources en même temps. Mais il y a toujours un nombre pertinent de demandes qui ne sont intéressées que Foo, et comme chacune Barest assez énorme en mémoire (environ 3x-4x la taille de Foo), je ne veux pas retourner un Barsi un client ne le demande pas explicitement.
ceran
De quelle taille parlons-nous? Je doute qu'il va faire que beaucoup de différence dans le temps de transfert, et je préfère une API propre sur la vitesse
Nathan Merrill