Recursos REST complexos / compostos / aninhados [fechado]


177

Estou tentando entender a melhor maneira de abordar conceitos em uma API baseada em REST. Recursos simples que não contêm outros recursos não são problema. Onde estou tendo problemas são os recursos complexos.

Por exemplo, eu tenho um recurso para uma história em quadrinhos. ComicBooktem todos os tipos de propriedades em-lo como author, issue number, date, etc.

Uma história em quadrinhos também tem uma lista de 1..ncapas. Essas capas são objetos complexos. Eles contêm muitas informações sobre a capa: o artista, uma data e até uma imagem codificada na base 64 da capa.

Para um GETem ComicBookque eu poderia apenas devolver os quadrinhos, e todas as tampas, incluindo as suas imagens base64'ed. Provavelmente isso não é grande coisa para conseguir uma única história em quadrinhos. Mas suponha que eu esteja criando um aplicativo cliente que queira listar todos os quadrinhos do sistema em uma tabela.
A tabela conterá algumas propriedades do ComicBookrecurso, mas certamente não queremos exibir todas as capas da tabela. O retorno de 1000 gibis, cada um com várias capas, resultaria em uma quantidade ridiculamente grande de dados, que não são necessários para o usuário final nesse caso.

Meu instinto é criar Coverum recurso e ter ComicBookcapas. Então agora Coveré um URI. GETnas histórias em quadrinhos funciona agora, em vez do enorme Coverrecurso, enviamos de volta um URI para cada capa e os clientes podem recuperar os recursos da capa conforme necessário.

Agora eu tenho um problema com a criação de novos quadrinhos. Certamente vou querer criar pelo menos uma capa quando criar uma Comic, na verdade essa é provavelmente uma regra de negócios.
Então agora eu estou preso, eu quer forçar os clientes a impor regras de negócio, em primeiro lugar submeter um Cover, ficando o URI para que a cobertura, então POSTing um ComicBookcom o URI na lista, ou a minha POSTon ComicBookleva em um recurso olhando diferente do que cospe Fora. Os recursos recebidos para POSTe GETsão cópias profundas, onde os GETs de saída contêm referências a recursos dependentes.

O Coverrecurso provavelmente é necessário em qualquer caso, porque tenho certeza que, como cliente, gostaria de abordar a direção em alguns casos. Portanto, o problema existe de forma geral, independentemente do tamanho do recurso dependente. Em geral, como você lida com recursos complexos sem forçar o cliente a apenas "saber" como esses recursos são compostos?


usar o RESTFUL SERVICE DISCOVERY faz sentido?
treecoder

1
Estou tentando aderir ao HATEAOS que, na minha opinião, é contrário ao uso de algo assim, mas vou dar uma olhada.
jgerman

Pergunta diferente no mesmo espírito. No entanto, a propriedade é diferente da sua solução proposta (a da pergunta). stackoverflow.com/questions/20951419/…
Wes

Respostas:


64

@ray, excelente discussão

@jgerman, não se esqueça que só porque é REST, não significa que os recursos precisam ser fixados em pedra no POST.

O que você escolher incluir em qualquer representação de um recurso é com você.

Seu caso das capas referenciadas separadamente é apenas a criação de um recurso pai (história em quadrinhos) cujos recursos filho (capas) podem ter referências cruzadas. Por exemplo, você também pode fornecer referências a autores, editores, caracteres ou categorias separadamente. Você pode criar esses recursos separadamente ou antes da revista em quadrinhos que os referencia como recursos filho. Como alternativa, você pode criar novos recursos filho após a criação do recurso pai.

Seu caso específico das capas é um pouco mais complexo, pois uma capa realmente exige uma história em quadrinhos e vice-versa.

No entanto, se você considerar uma mensagem de email como um recurso e o endereço de origem como um recurso filho, obviamente ainda poderá referenciar o endereço de origem separadamente. Por exemplo, obtenha tudo dos endereços. Ou crie uma nova mensagem com um endereço anterior. Se o email fosse REST, você poderia ver facilmente que muitos recursos de referência cruzada estavam disponíveis: / mensagens recebidas, / mensagens de rascunho, / de endereços, / para endereços, / endereços, assuntos / anexos / pastas , / tags, / categorias, / etiquetas, et al.

Este tutorial fornece um ótimo exemplo de recursos com referência cruzada. http://www.peej.co.uk/articles/restfully-delicious.html

Esse é o padrão mais comum para dados gerados automaticamente. Por exemplo, você não publica um URI, ID ou data de criação para o novo recurso, pois eles são gerados pelo servidor. E, no entanto, você pode recuperar o URI, o ID ou a data de criação quando recuperar o novo recurso.

Um exemplo no seu caso de dados binários. Por exemplo, você deseja postar dados binários como recursos filho. Quando você obtém o recurso pai, pode representar esses recursos filhos como os mesmos dados binários ou como URIs que representam os dados binários.

Formulários e parâmetros já são diferentes das representações HTML dos recursos. A publicação de um parâmetro binário / arquivo que resulta em um URL não é um exagero.

Quando você obtém o formulário para um novo recurso (/ histórias em quadrinhos / novo) ou obtém o formulário para editar um recurso (/ histórias em quadrinhos / 0 / edição), solicita uma representação específica do formulário. Se você publicá-lo na coleção de recursos com o tipo de conteúdo "application / x-www-form-urlencoded" ou "multipart / form-data", você está solicitando ao servidor que salve essa representação de tipo. O servidor pode responder com a representação HTML que foi salva ou o que seja.

Você também pode permitir que uma representação HTML, XML ou JSON seja postada na coleção de recursos, para fins de uma API ou similar.

Também é possível representar seus recursos e fluxo de trabalho conforme você descreve, levando em consideração as capas publicadas após a história em quadrinhos, mas exigindo que as histórias em quadrinhos tenham uma capa. Exemplo da seguinte maneira.

  • Permite criação de capa atrasada
  • Permite a criação de histórias em quadrinhos com a capa necessária
  • Permite que as capas sejam cruzadas
  • Permite várias capas
  • Criar rascunho de quadrinhos
  • Criar rascunhos de capas de quadrinhos
  • Publicar rascunho de quadrinhos

GET / histórias em quadrinhos
=> 200 OK, obtenha todas as histórias em quadrinhos.

GET / comic-books / 0
=> 200 OK, Obter gibi (id: 0) com capas (/ capas / 1, / capas / 2).

GET / histórias em quadrinhos / 0 / capas
=> 200 OK, obtenha capas para histórias em quadrinhos (id: 0).

GET / covers
=> 200 OK, obtenha todas as capas.

GET / covers / 1
=> 200 OK, Obter capa (id: 1) com histórias em quadrinhos (/ comic-books / 0).

GET / comic-books / new
=> 200 OK, Obter formulário para criar quadrinhos (formulário: POST / draft-comic-books).

POST / draft-comic-books
title = foo
author = boo
publisher = goo
publicado = 2011-01-01
=> 302 Encontrados, Localização: / draft-comic-books / 3, Redirecionar para rascunho de quadrinhos (id: 3) com capas (binárias).

GET / draft-comic-books / 3
=> 200 OK, obtenha rascunho de quadrinhos (id: 3) com capas.

GET / draft-comic-books / 3 / covers
=> 200 OK, obtenha capas para rascunho de quadrinhos (/ draft-comic-book / 3).

GET / draft-comic-books / 3 / covers / new
=> 200 OK, Obter formulário para criar uma capa para rascunho de quadrinhos (/ draft-comic-book / 3) (formulário: POST / draft-comic-books / 3 / capas).

POST / draft-comic-books / 3 / covers
cover_type = front
cover_data = (binary)
=> 302 Encontrado, Localização: / draft-comic-books / 3 / covers, Redirecionar para nova capa para rascunho de quadrinhos (/ draft-comic - livro / 3 / capas / 1).

GET / draft-comic-books / 3 / publish
=> 200 OK, Obter formulário para publicar o rascunho da revista em quadrinhos (id: 3) (formulário: POST / publicado-histórias em quadrinhos).

POST / comic-comic-
title title = foo
autor = boo
publisher = goo
publicado =
01-01-2011 cover_type = front
cover_data = (binário)
=> 302 Encontrado, Localização: / comic-books / 3, Redirecionar para publicação em quadrinhos (id: 3) com capas.


Sou totalmente novato nisso, e estou tentando aprender com pressa. Achei isso extremamente útil. No entanto, nos outros blogs, etc. Eu tenho lido hoje, o uso de GET para executar uma operação (particularmente uma operação que não é idempotente) seria desaprovado. Então não deveria ser POST / draft-comic-books / 3 / publish?
Gary McGill

3
@GaryMcGill No exemplo dele, / draft-comic-books / 3 / publish retorna apenas um formulário HTML (não modifica nenhum dado).
Olivier Lalonde

@ Olivier está correto. A palavra publicar está lá para indicar o que o formulário faz. No entanto, como você deseja manter os verbos restritos aos métodos HTTP, você deve postar em um recurso para histórias em quadrinhos publicadas. ... Se este fosse um site, você pode precisar de um URI para o formulário publicar algo. ... Embora, se a ação de publicação fosse apenas um botão na página de quadrinhos, esse formulário de botão único poderia ser postado diretamente no URI / publicado em quadrinhos.
Alex

@Alex, na solicitação POST, em vez disso, retornaria um 201 Created, com a URL do novo recurso como Location nos cabeçalhos de resposta.
Ismriv

2
@ Stephanie, redirecionamentos apenas tornam tudo mais simples para os controladores. Mesmo para uma API, é mais simples fazer com que o controller create retorne o local para o novo conteúdo e, em seguida, deixe o controller show controlar a exibição do novo conteúdo. No entanto, é melhor / mais simples para o cliente da API obter o conteúdo e não se preocupar com redirecionamentos.
Alex

45

Tratar as capas como recursos está definitivamente no espírito do REST, particularmente no HATEOAS. Portanto, sim, uma GETsolicitação para http://example.com/comic-books/1fornecer uma representação do livro 1, com propriedades que incluem um conjunto de URIs para capas. Por enquanto, tudo bem.

Sua pergunta é como lidar com a criação de quadrinhos. Se sua regra de negócios fosse que um livro tivesse 0 ou mais capas, você não terá problemas:

POST http://example.com/comic-books

com dados de histórias em quadrinhos sem capa criará uma nova história em quadrinhos e retornará o ID gerado pelo servidor (digamos que volte como 8), e agora você pode adicionar capas da seguinte maneira:

POST http://example.com/comic-books/8/covers

com a cobertura no corpo da entidade.

Agora você tem uma boa pergunta: o que acontece se sua regra de negócios diz que sempre deve haver pelo menos uma cobertura. Aqui estão algumas opções, a primeira das quais você identificou na sua pergunta:

  1. Forçar a criação de uma capa primeiro, agora tornando a capa essencialmente um recurso não dependente, ou você coloca a capa inicial no corpo da entidade do POST que cria a história em quadrinhos. Como você diz, significa que a representação que você POST criar será diferente da representação que você GET.

  2. Defina a noção de uma capa primária, inicial, preferida ou designada de outra forma. Provavelmente, isso é um truque de modelagem, e se você fizesse isso, seria como ajustar seu modelo de objeto (seu modelo conceitual ou de negócios) para ajustar-se a uma tecnologia. Não é uma ótima ideia.

Você deve pesar essas duas opções contra simplesmente permitir quadrinhos sem capa.

Qual das três opções você deve fazer? Não sabendo muito sobre sua situação, mas responda à pergunta geral sobre recursos dependentes 1..N, eu diria:

  • Se você pode usar 0..N para a camada de serviço RESTful, ótimo. Talvez uma camada entre sua RESTful SOA possa lidar com outras restrições comerciais, se pelo menos uma for necessária. (Não tenho certeza de como isso seria, mas pode valer a pena explorar ... os usuários finais geralmente não veem a SOA de qualquer maneira.)

  • Se você simplesmente precisar modelar uma restrição 1..N, pergunte-se se as capas podem ser apenas recursos compartilháveis, em outras palavras, elas podem existir em outras coisas que não os quadrinhos. Agora eles não são recursos dependentes e você pode criá-los primeiro e fornecer URIs no seu POST que cria gibis.

  • Se você precisar de 1..N e as capas permanecem dependentes, simplesmente relaxe seu instinto para manter as representações no POST e GET iguais, ou faça-as iguais.

O último item é explicado da seguinte maneira:

<comic-book>
  <name>...</name>
  <edition>...</edition>
  <cover-image>...BASE64...</cover-image>
  <cover-image>...BASE64...</cover-image>
  <cover>...URI...</cover>
  <cover>...URI...</cover>
</comic-book>

Ao fazer o POST, você permite os uris existentes, se os tiver (emprestado de outros livros), mas também coloca uma ou mais imagens iniciais. Se você estiver criando um livro e sua entidade não tiver uma imagem de capa inicial, retorne uma resposta 409 ou semelhante. Em GET, você pode retornar URIs.

Então, basicamente, você está permitindo que as representações POST e GET "sejam as mesmas", mas você escolhe não "usar" a imagem da capa no GET nem na POST. Espero que isso faça sentido.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.