Uma API REST pode retornar vários recursos como um único recurso composto?

9

Estou no processo de criação de uma API REST e, atualmente, estou encontrando o seguinte problema:

  • Fooé o primeiro recurso. As operações CRUD podem ser aplicadas via /foo/URI.
  • Baré o segundo recurso. As operações CRUD podem ser aplicadas via /bar/URI.
  • Todos Fooestão associados a zero ou um Bar. A razão pela qual eu não trato Barcomo um sub-recurso de Fooé porque a mesma Barinstância pode ser compartilhada entre vários Foo. Por isso, achei melhor acessá-lo por meio de um URI independente em vez de /foo/[id]/bar.

Meu problema é que, em uma quantidade significativa de casos, os clientes que solicitam uma Fooinstância também estão interessados ​​na Barinstância associada . Atualmente, isso significa que eles precisam executar duas consultas em vez de uma. Quero apresentar uma maneira que permita obter os dois objetos com uma única consulta, mas não sei como modelar a API para fazer isso. O que eu vim até agora:

  • Eu poderia introduzir um parâmetro de consulta semelhante a este: /foo/[id]?include_bar=true. O problema dessa abordagem é que a representação do recurso (por exemplo, a estrutura JSON) da resposta precisaria parecer diferente (por exemplo, um contêiner, como em { foo: ..., bar: ... }vez de apenas um serializado Foo), o que torna o Fooponto final do recurso "heterogêneo". Eu não acho isso bom. Ao consultar /foo, os clientes sempre devem obter a mesma representação de recurso (estrutura), independentemente dos parâmetros de consulta.
  • Outra idéia é introduzir um novo ponto de extremidade somente leitura, por exemplo /fooandbar/[foo-id]. Nesse caso, não há problema em retornar uma representação como { foo: ..., bar: ... }, porque é apenas a representação "oficial" do fooandbarrecurso. No entanto, não sei se esse ponto de extremidade auxiliar é realmente RESTful (foi por isso que escrevi "can" no título da pergunta. É claro que é tecnicamente possível, mas não sei se é uma boa idéia).

O que você acha? há outras possibilidades?

ceran
fonte
Qual é o termo para o relacionamento entre Foo e Bar? Você poderia dizer que Bar é pai de Foo?
Nathan Merrill
A Barnão pode existir sem estar associado a a Foo. No entanto, como escrevi acima, é possível que vários Foos compartilhem o mesmo Bar. Deveria ser possível criar um Foosem um Barassociado, por isso acho que não Bardeveria ser tratado como pai.
ceran
11
Acho que você está enfrentando alguns dos problemas que tive ao traduzir diretamente relacionamentos de modelo de domínio em URIs e equiparar recursos a entidades de domínio . Pode interessar que as APIs REST devem ser orientadas por hipertexto . Atenção especial para o 4º ponto
Laiv 10/11/16

Respostas:

5

Uma API REST de nível 3 retornará um Fooe também um link indicando o relacionado Bar.

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

Você pode adicionar um recurso "drill down" à sua API que permite a navegação de links;

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

O recurso de pesquisa detalhada fica na frente das APIs e intercepta as respostas. Ele faria as chamadas de busca detalhada e preenchia os detalhes antes de devolver a resposta ao chamador.

Isso é bastante comum no REST de nível 3, pois reduz bastante a conversação de cliente / servidor em http lento. A empresa em que trabalho produz uma API REST de nível 3 com exatamente esse recurso.

Atualização: quanto vale a pena, veja como ele pode parecer no JSON. É assim que nossa API a estruturaria. Observe que você pode aninhar suas pesquisas detalhadas para obter links de links 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
fonte
Interessante, eu já estou usando links / controles de hipermídia para remover o acoplamento rígido aos URIs, mas não pensei na idéia de "detalhamento" que parece muito promissora. Como seria uma representação JSON No momento, cada representação JSON dos meus recursos contém uma linksmatriz, cada entrada é um objeto de link com relum uricampo e um (semelhante ao seu exemplo de xml). Devo apenas adicionar um terceiro campo a cada objeto de link (por exemplo data)? Existe algum padrão?
ceran
O detalhamento não é realmente um recurso de descanso, então não há padrões (pelo menos que eu saiba).
Qwerky
Existem alguns padrões propostos, como stateless.co/hal_specification.html que estou usando em nossos aplicativos. É muito próximo do seu exemplo.
Pete Kirkham
4

Se 95% de todas as consultas deseja Foo, bem como Bar, em seguida, simplesmente devolvê-lo dentro do Fooobjeto quando você solicitar um Foo. Basta adicionar uma propriedade bar(ou algum outro termo para o relacionamento) e colocar o Barobjeto lá. Se o relacionamento não existir, use null.

Eu acho que você está pensando demais nisso :)

Nathan Merrill
fonte
Eu não deveria ter chegado a esse número (95%), foi um erro, desculpe. O que eu queria dizer era que grande parte das solicitações está interessada nos dois recursos ao mesmo tempo. Mas ainda há um número relevante de solicitações que apenas interessam Fooe, uma vez que cada uma delas Baré bastante grande na memória (cerca de 3x-4x o tamanho Foo), não quero retornar a Barse um cliente não solicitar explicitamente.
ceran
Quão grande estamos falando? Eu duvido que ele vai fazer que muita diferença no tempo de transferência, e eu prefiro uma API limpa sobre a velocidade
Nathan Merrill