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. ComicBook
tem 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..n
capas. 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 GET
em ComicBook
que 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 ComicBook
recurso, 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 Cover
um recurso e ter ComicBook
capas. Então agora Cover
é um URI. GET
nas histórias em quadrinhos funciona agora, em vez do enorme Cover
recurso, 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 POST
ing um ComicBook
com o URI na lista, ou a minha POST
on ComicBook
leva em um recurso olhando diferente do que cospe Fora. Os recursos recebidos para POST
e GET
são cópias profundas, onde os GET
s de saída contêm referências a recursos dependentes.
O Cover
recurso 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?
fonte
Respostas:
@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.
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.
fonte
Tratar as capas como recursos está definitivamente no espírito do REST, particularmente no HATEOAS. Portanto, sim, uma
GET
solicitação parahttp://example.com/comic-books/1
fornecer 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:
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:
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:
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.
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:
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.
fonte