web-dev-qa-db-fra.com

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

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

  • Foo est la première ressource. Les opérations CRUD peuvent être appliquées via le /foo/ URI.
  • Bar est la deuxième ressource. Les opérations CRUD peuvent être appliquées via le /bar/ URI.
  • Chaque Foo est associé à zéro ou un Bar. La raison pour laquelle je ne traite pas Bar comme une sous-ressource de Foo est parce que la même instance Bar peut être partagée entre plusieurs Foos. 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 instance Foo sont également intéressés par l'instance Bar 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 que { foo: ..., bar: ... } au lieu d'un Foo) sérialisé, ce qui rend le point de terminaison de la ressource Foo "hétérogène". Je ne pense pas que ce soit une bonne chose. Lors de l'interrogation /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 ex. /fooandbar/[foo-id]. Dans ce cas, ce n'est pas un problème pour retourner une représentation comme { foo: ..., bar: ... }, car alors c'est juste la représentation "officielle" de la ressource fooandbar. 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?

9
ceran

Un niveau REST vous renverrait un Foo ainsi qu'un lien indiquant le Bar correspondant.

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 au niveau 3 REST car cela réduit beaucoup les conversations client/serveur sur http lent. La société pour laquelle je travaille produit un niveau 3 REST API avec exactement cette fonctionnalité.

Mise à jour: Pour ce que ça vaut, voici à quoi cela pourrait ressembler dans JSON. C'est ainsi que notre API le structurerait. Notez que vous pouvez imbriquer vos analyses 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..
        }
      ]
    }
  ]
}
5
Qwerky

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

Je pense que vous y pensez trop :)

4
Nathan Merrill