Complexo REST / Recursos Compostos / Aninhados

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

Por exemplo, eu tenho um recurso para o ComicBook. ComicBook tem todos os tipos de propriedades como autor, número de edição, data, etc.

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

Para um GET no ComicBook eu poderia apenas devolver o quadrinho, e todas as capas, incluindo suas imagens de base. Isso provavelmente não é um grande negócio para conseguir um único quadrinho. Mas suponha que eu esteja criando um aplicativo cliente que queira listar todos os quadrinhos no sistema em uma tabela. A tabela conterá algumas propriedades do recurso ComicBook, mas certamente não queremos exibir todas as capas na tabela. O retorno de 1000 gibis, cada um com várias capas, resultaria em uma quantidade ridiculamente grande de dados, dados que não são necessários para o usuário final nesse caso.

Meu instinto é fazer da Cover um recurso e o ComicBook contém capas. Então agora Cover é um URI. OBTENHA em quadrinhos funciona agora, em vez do enorme recurso Cover, enviamos um URI para cada capa e os clientes podem recuperar os resources da capa conforme necessário.

Agora eu tenho um problema com a criação de novos quadrinhos. Certamente eu vou querer criar pelo menos uma capa quando eu criar um Comic, na verdade isso é provavelmente uma regra de negócios. Então, agora estou preso, ou forço os clientes a impor regras de negócios, primeiro enviando uma Capa, obtendo o URI para essa capa, depois POSTando um ComicBook com esse URI na lista, ou meu POST no ComicBook tem uma aparência diferente Recurso que ele cospe. Os resources de input para POST e GET são cópias profundas, em que os GETs de saída contêm referências a resources dependentes.

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

@ray, excelente discussão

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

O que você escolhe include em qualquer representação de um recurso depende de você.

Seu caso das capas referenciadas separadamente é meramente a criação de um recurso pai (quadrinhos) cujos resources filhos (capas) podem ter referência cruzada. Por exemplo, você também pode fornecer referências a autores, editores, personagens ou categorias separadamente. Você pode desejar criar esses resources separadamente ou antes da revista em quadrinhos que os referencia como resources infantis. Como alternativa, você pode desejar criar novos resources filhos após a criação do recurso pai.

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

No entanto, se você considerar uma mensagem de e-mail como um recurso e o endereço de como um recurso filho, obviamente ainda poderá referenciar o endereço de separadamente. Por exemplo, obtenha todos os endereços. Ou crie uma nova mensagem com um endereço anterior. Se o email fosse REST, você poderia facilmente ver que muitos resources de referência cruzada poderiam estar disponíveis: / messages recebidas, / mensagens de rascunho, / de endereços, / para endereços, / endereços, / assuntos, / anexos, / pastas , / tags, / categories, / labels, et al.

Este tutorial fornece um ótimo exemplo de resources de 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 ao recuperar o novo recurso.

Um exemplo no seu caso de dados binários. Por exemplo, você deseja publicar dados binários como resources filhos. Quando você obtém o recurso pai, pode representar esses resources 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 resources. Postar um parâmetro binário / arquivo que resulta em um URL não é um trecho.

Quando você obtém o formulário para um novo recurso (/ quadrinhos / novo) ou obtém o formulário para editar um recurso (/ comic-books / 0 / edit), você está solicitando uma representação específica do recurso. Se você publicá-lo na coleção de resources com o tipo de conteúdo “application / x-www-form-urlencoded” ou “multipart / form-data”, estará solicitando ao servidor para salvar essa representação de tipo. O servidor pode responder com a representação HTML que foi salva ou o que for.

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

Também é possível representar seus resources e stream de trabalho conforme descreve, levando em conta capas publicadas após a revista em quadrinhos, mas exigindo que as revistas em quadrinhos tenham uma capa. Exemplo como segue.

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

GET / comic-books
=> 200 OK, obtenha todos os quadrinhos.

GET / histórias em quadrinhos / 0
=> 200 OK, Obter quadrinhos (id: 0) com capas (/ capas / 1, / capas / 2).

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

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

GET / covers / 1
=> 200 OK, Obter capa (id: 1) com história 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
autor = boo
publisher = goo
publicado em 2011-01-01
=> 302 Encontrado, Localização: / draft-comic-books / 3, Redirecionar para esboçar quadrinhos (id: 3) com capas (binário).

GET / rascunho-histórias em quadrinhos / 3
=> 200 OK, Obter rascunho de quadrinhos (id: 3) com capas.

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

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

POST / draft-comic-books / 3 / capas
cover_type = frente
cover_data = (binário)
=> 302 encontrado, localização: / rascunho-comic-books / 3 / covers, redirect a nova capa para o projecto de banda desenhada (/ draft-comic-book / 3 / covers / 1).

GET / draft-comic-books / 3 / publicar
=> 200 OK, Obter formulário para publicar rascunho de quadrinhos (id: 3) (formulário: POST / published-comic-books).

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

Tratar as capas como resources está definitivamente no espírito do REST, particularmente HATEOAS. Então, sim, uma solicitação GET para http://example.com/comic-books/1 forneceria 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ócio é que um livro teria 0 ou mais capas, você não terá problemas:

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

Com os dados da revista em quadrinhos sem capa, você criará uma nova revista em quadrinhos e retornará a ID gerada pelo servidor (digamos que ela volte como 8), e agora você pode adicionar capas a ela da seguinte forma:

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

com a capa no corpo da entidade.

Agora você tem uma boa pergunta, que é o que acontece se sua regra de negócio diz que sempre deve haver pelo menos uma capa. 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, essencialmente, fazer da capa um recurso não dependente ou colocar a capa inicial no corpo da entidade do POST que cria a banda desenhada. Isso, como você diz, significa que a representação que você POSTE para criar diferirá da representação que você GET.

  2. Defina a noção de uma capa primária, inicial, preferida ou designada. Este é provavelmente um hack de modelagem, e se você fez isso, seria como ajustar seu modelo de object (seu modelo conceitual ou de negócios) para ajustar uma tecnologia. Não é uma ótima idéia.

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

Qual das três opções você deve tomar? Não sabendo muito sobre a sua situação, mas respondendo a questão geral de recurso dependente 1.N, eu diria:

  • Se você puder ir com 0..N para sua camada de serviço RESTful, ótimo. Talvez uma camada entre sua RESTful SOA possa manipular a restrição de negócios adicional, se pelo menos uma for necessária. (Não tenho certeza de como isso ficaria, mas talvez valha a pena explorar … os usuários finais geralmente não vêem o SOA de qualquer maneira.)

  • Se você simplesmente precisa modelar uma restrição 1.N, então, pergunte a si mesmo se os covers podem ser apenas resources compartilháveis, em outras palavras, eles podem existir em outras coisas além de gibis. Agora eles não são resources dependentes e você pode criá-los primeiro e fornecer URIs em seu POST que cria quadrinhos.

  • Se você precisar de 1.N e as coberturas continuarem dependentes, simplesmente relaxe seu instinto para manter as representações no POST e GET o mesmo, ou torná-las as mesmas.

O último item é explicado da seguinte forma:

  ... ... ...BASE64... ...BASE64... ...URI... ...URI...  

Quando você faz o POST, você permite uris existentes se você as tiver (emprestadas de outros livros), mas também colocar 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ê simplesmente não escolhe “usar” a imagem de capa no GET nem cobre o POST. Espero que isso faça sentido.