Eu tenho uma hierarquia de objetos que preciso expor por meio de uma API RESTful e não tenho certeza de como meus URLs devem ser estruturados e o que eles devem retornar. Não consegui encontrar as melhores práticas.
Digamos que eu tenha cães e gatos herdando de animais. Preciso de operações CRUD em cães e gatos; Eu também quero fazer operações em animais em geral.
Minha primeira ideia era fazer algo assim:
GET /animals # get all animals
POST /animals # create a dog or cat
GET /animals/123 # get animal 123
Acontece que a coleção / animals passou a ser "inconsistente", pois pode retornar e retirar objetos que não possuem exatamente a mesma estrutura (cães e gatos). É considerado "RESTful" ter uma coleção que retorna objetos com atributos diferentes?
Outra solução seria criar um URL para cada tipo concreto, assim:
GET /dogs # get all dogs
POST /dogs # create a dog
GET /dogs/123 # get dog 123
GET /cats # get all cats
POST /cats # create a cat
GET /cats/123 # get cat 123
Mas agora a relação entre cães e gatos está perdida. Se alguém deseja recuperar todos os animais, os recursos do cão e do gato devem ser consultados. O número de URLs também aumentará com cada novo subtipo de animal.
Outra sugestão era aumentar a segunda solução adicionando isto:
GET /animals # get common attributes of all animals
Nesse caso, os animais retornados conteriam apenas atributos comuns a todos os animais, descartando atributos específicos para cães e gatos. Isso permite recuperar todos os animais, embora com menos detalhes. Cada objeto retornado pode conter um link para a versão detalhada e concreta.
Algum comentário ou sugestão?
fonte
GET /animals - gets all dogs and cats
GET /animals/dogs - gets all dogs
GET /animals/cats - gets all cats
GET /animals
Aceitarapplication/vnd.vet-services.animal.dog+json
POST
operação, já que a maioria dos frameworks não saberia como desserializar corretamente em um modelo, já que json não carrega boas informações de digitação. Como você lidaria com casos de postagem, por exemplo[{"type":"dog","name":"Fido","playsFetch":true},{"type":"cat","name":"Sparkles","likesToPurr":"sometimes"}
?Essa pergunta pode ser melhor respondida com o suporte de um aprimoramento recente introduzido na versão mais recente do OpenAPI.
Foi possível combinar esquemas usando palavras-chave como oneOf, allOf, anyOf e obter uma carga útil de mensagem validada desde o esquema JSON v1.0.
https://spacetelescope.github.io/understanding-json-schema/reference/combining.html
No entanto, no OpenAPI (antigo Swagger), a composição de esquemas foi aprimorada pelo discriminador de palavras-chave (v2.0 +) e oneOf (v3.0 +) para oferecer suporte verdadeiro ao polimorfismo.
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaComposition
Sua herança pode ser modelada usando uma combinação de oneOf (para escolher um dos subtipos) e allOf (para combinar o tipo e um de seus subtipos). Abaixo está uma definição de amostra para o método POST.
fonte
Eu iria para / animals retornando uma lista de cães e peixes e o que mais:
Deve ser fácil implementar um exemplo JSON semelhante.
Os clientes podem sempre contar com a presença do elemento "nome" (um atributo comum). Mas, dependendo do atributo "tipo", haverá outros elementos como parte da representação animal.
Não há nada inerentemente RESTful ou unRESTful em retornar tal lista - REST não prescreve nenhum formato específico para representar dados. Tudo o que diz é que os dados devem ter alguma representação e o formato para essa representação é identificado pelo tipo de mídia (que em HTTP é o cabeçalho Content-Type).
Pense em seus casos de uso - você precisa mostrar uma lista de animais mistos? Bem, então retorne uma lista de dados mistos de animais. Você precisa de uma lista apenas de cães? Bem, faça essa lista.
Se você faz / animals? Type = dog ou / dogs é irrelevante em relação ao REST, que não prescreve nenhum formato de URL - isso é deixado como um detalhe de implementação fora do escopo do REST. REST apenas afirma que os recursos devem ter identificadores - não importa o formato.
Você deve adicionar alguns links de hipermídia para se aproximar de uma API RESTful. Por exemplo, adicionando referências aos detalhes do animal:
Ao adicionar links de hipermídia, você reduz o acoplamento cliente / servidor - no caso acima, você tira o fardo da construção de URL do cliente e deixa o servidor decidir como construir URLs (dos quais ele, por definição, é a única autoridade).
fonte
De fato, mas tenha em mente que o URI simplesmente nunca reflete relações entre objetos.
fonte
Eu sei que esta é uma questão antiga, mas estou interessado em investigar mais problemas em uma modelagem de herança RESTful
Sempre posso dizer que cachorro é animal e galinha também, mas galinha faz ovos enquanto cachorro é mamífero, então não pode. Uma API como
OBTER animais /: ID animal / ovos
não é consistente porque indica que todos os subtipos de animais podem ter ovos (como consequência da substituição de Liskov). Haveria um fallback se todos os mamíferos respondessem com '0' a esta solicitação, mas e se eu também habilitar um método POST? Devo ter medo de que amanhã haja ovos de cachorro em meus crepes?
A única maneira de lidar com esses cenários é fornecer um 'super-recurso' que agrega todos os sub-recursos compartilhados entre todos os 'recursos derivados' possíveis e, em seguida, uma especialização para cada recurso derivado que precisa dele, assim como quando fazemos o downcast de um objeto em oop
GET / animals /: animalID / sons GET / hens /: animalID / eggs POST / hens /: animalID / eggs
A desvantagem, aqui, é que alguém poderia passar um ID de cachorro para fazer referência a uma instância de coleção de galinhas, mas o cachorro não é uma galinha, então não seria incorreto se a resposta fosse 404 ou 400 com uma mensagem de motivo
Estou errado?
fonte
Sim, você está errado. Também os relacionamentos podem ser modelados seguindo as especificações da OpenAPI, por exemplo, desta forma polimórfica.
...
fonte
GET chicken/eggs
também deve funcionar usando os geradores de código OpenAPI comuns para os controladores, mas eu não verifiquei isso ainda. Talvez alguém possa tentar?