Tenho três perguntas sobre o design da API REST que espero que alguém possa esclarecer. Pesquisei incansavelmente por muitas horas, mas não encontrei respostas para minhas perguntas em nenhum lugar (talvez eu simplesmente não saiba o que procurar?).
Questão 1
Minha primeira pergunta tem a ver com ações / RPC. Desenvolvo uma API REST há algum tempo e estou acostumado a pensar nas coisas em termos de coleções e recursos. No entanto, deparei-me com alguns casos em que o paradigma parece não se aplicar e estou me perguntando se existe uma maneira de conciliar isso com o paradigma REST.
Especificamente, tenho um caso em que a modificação de um recurso faz com que um email seja gerado. No entanto, posteriormente, o usuário pode indicar especificamente que deseja reenviar o email enviado anteriormente. Ao reenviar o email, nenhum recurso é modificado. Nenhum estado é alterado. É simplesmente uma ação que precisa ocorrer. A ação está vinculada ao tipo de recurso específico.
É apropriado misturar algum tipo de chamada de ação com um URI de recurso (por exemplo /collection/123?action=resendEmail
)? Seria melhor especificar a ação e passar o ID do recurso para ela (por exemplo /collection/resendEmail?id=123
)? Esta é a maneira errada de fazer isso? Tradicionalmente (pelo menos com HTTP), a ação que está sendo executada é o método de solicitação (GET, POST, PUT, DELETE), mas eles realmente não permitem ações personalizadas com um recurso.
Questão 2
Eu uso a parte da URL da querystring para filtrar o conjunto de recursos retornados ao consultar uma coleção (por exemplo /collection?someField=someval
). Dentro do meu controlador de API, eu determino que tipo de comparação ele fará com esse campo e valor. Eu descobri que isso realmente não funciona. Eu preciso de uma maneira de permitir que o usuário da API especifique o tipo de comparação que deseja executar.
A melhor idéia que tive até agora é permitir que o usuário da API o especifique como um apêndice ao nome do campo (por exemplo /collection?someField:gte=someval
- para indicar que ele deve retornar recursos onde someField
é maior ou igual a (> =) o que someval
for Essa é uma boa idéia? Uma má idéia? Se sim, por quê? Existe uma maneira melhor de permitir que o usuário especifique o tipo de comparação a ser executado com o campo e valor fornecidos?
Questão 3
Costumo ver URIs que se parecem /person/123/dogs
com obter os person
s dogs
. Geralmente evito algo assim porque, no final, acho que, ao criar um URI como esse, você está apenas acessando uma dogs
coleção filtrada por um person
ID específico . Seria equivalente a /dogs?person=123
. Existe realmente uma boa razão para um URI REST ter mais de dois níveis de profundidade ( /collection/resource_id
)?
Respostas:
Prefiro modelar isso de uma maneira diferente, com uma coleção de recursos representando os emails que devem ser enviados; o envio será processado pelos internos do serviço em devido tempo, quando o recurso correspondente será removido. (Ou o usuário pode EXCLUIR o recurso mais cedo, causando o cancelamento da solicitação para enviar.)
Faça o que fizer, não coloque verbos no nome do recurso! Esse é o substantivo (e a parte da consulta é o conjunto de adjetivos). Verbos verbais esquisitos REST!
Prefiro especificar uma cláusula de filtro geral e tê-la como um parâmetro de consulta opcional em qualquer solicitação para buscar o conteúdo da coleção. O cliente pode especificar exatamente como restringir o conjunto retornado, da maneira que desejar. Eu também me preocuparia um pouco com a descoberta da linguagem de filtro / consulta; quanto mais rico, mais difícil é descobrir clientes arbitrários. Uma abordagem alternativa que, pelo menos teoricamente, lida com esse problema de descoberta é permitir a criação de sub-recursos de restrição da coleção, que os clientes obtêm ao postar um documento descrevendo a restrição ao recurso de coleção. Ainda é um abuso leve, mas pelo menos é um que você pode claramente tornar descoberto!
Esse tipo de descoberta é uma das coisas que acho menos forte com o REST.
Quando a coleção aninhada é realmente um sub-recurso das entidades membros da coleção externa, é razoável estruturá-las como um sub-recurso. Por "sub-recurso", quero dizer algo como a relação de composição UML, onde destruir o recurso externo naturalmente significa destruir a coleção interna.
Outros tipos de coleção podem ser modelados como um redirecionamento HTTP; portanto,
/person/123/dogs
pode realmente ser respondido fazendo um 307 que redireciona para/dogs?person=123
. Nesse caso, a coleção não é realmente composição UML, mas agregação UML. A diferença importa; é significativo!fonte
resendEmail
ação possa ser tratada com a criação de uma coleção e o POST, ela parece menos natural. Na verdade, não guardo nada no banco de dados quando um e-mail é reenviado (sem necessidade). Nenhum recurso é modificado; portanto, é simplesmente uma ação que é bem-sucedida ou falha. Não pude retornar um ID de recurso existente além da vida útil da chamada, tornando essa implementação um hack em vez de ser RESTful. Simplesmente não é uma operação CRUD.É compreensível ficar um pouco confuso sobre como usar corretamente o REST com base em todas as maneiras pelas quais grandes empresas projetam suas APIs REST.
Você está certo de que o REST é um sistema de Coleta de Recursos. Significa Representational State Transfer. Não é uma ótima definição se você me perguntar. Mas os principais conceitos são os 4 VERBs HTTP e são sem estado.
O ponto importante a ser observado é que você só possui 4 VERBOS com REST. Estes são GET, POST, PUT e DELETE. Seu
resend
exemplo seria adicionar um novo verbo ao REST. Isso deve ser uma bandeira vermelha.Questão 1
É importante perceber que o responsável pela chamada da API REST não deve saber que executar um
PUT
em sua coleção resultaria na geração de um email. Isso cheira a um vazamento para mim. O que eles poderiam saber é que executar umPUT
poderia resultar em tarefas extras que eles poderiam consultar posteriormente. Eles saberiam disso executando umGET
recurso recentemente criado. IssoGET
retornaria o recurso e todos osTask
IDs de recurso associados a ele. Posteriormente, você poderá consultar essas tarefas para determinar seu status e até enviar um novoTask
.Você tem poucas opções.
REST - Abordagem baseada em recursos de tarefas
Crie um
tasks
recurso no qual você possa enviar tarefas específicas ao seu sistema para executar ações. Você pode executarGET
a tarefa com base naID
devolução para determinar seu status.Ou você pode misturar um
SOAP over HTTP
serviço da Web para adicionar algum RPC à sua arquitetura.consultando todas as tarefas para um recurso específico
GET http://server/api/myCollection/123/tasks
exemplo de recurso de tarefa
PUT http://server/api/tasks
==> retorna o ID da tarefa
223334
GET http://server/api/tasks/223334
REST - Usando o POST para acionar ações
Você sempre pode
POST
dados adicionais para um recurso. Na minha opinião, isso violaria o espírito do REST, mas ainda seria compatível.Você pode fazer um POST semelhante a este:
POST http://server/api/collection/123
{ "action" : "send-email" }
Você estará atualizando o recurso 123 da coleção com dados adicionais. Esses dados adicionais são essencialmente uma ação que solicita ao back-end que envie um email para esse recurso.
O problema que tenho com isso é que um
GET
recurso retornará esses dados atualizados. No entanto, isso resolveria seus requisitos e ainda seria RESTful.SOAP - Serviço da Web que aceita recursos obtidos do REST
Crie um novo WebService no qual você pode enviar emails com base no ID do recurso anterior da API REST. Não entrarei em detalhes sobre o SOAP aqui, pois a pergunta original é sobre o REST e esses dois conceitos / tecnologias não devem ser comparados, pois são maçãs e laranjas .
Questão 2
Você também tem algumas opções aqui:
Parece que muitas empresas maiores que publicam APIs REST expõem uma
search
coleção que é realmente apenas uma maneira de passar parâmetros de consulta para retornar recursos.GET http://server/api/search?q="type = myCollection & someField >= someval"
O que retornaria uma coleção de recursos REST totalmente qualificados, como:
Ou você pode permitir algo como MVEL como um parâmetro de consulta.
Questão 3
Eu prefiro os subníveis do que ter que voltar e consultar o outro recurso com um parâmetro de consulta. Não acredito que exista alguma regra de uma maneira ou de outra. Você pode implementar os dois lados e permitir que o chamador decida qual é o mais apropriado com base em como eles entraram no sistema pela primeira vez.
Notas
Discordo dos comentários de legibilidade de outras pessoas. Apesar do que alguns possam pensar, o REST ainda não é para consumo humano. É para consumo da máquina. Se eu quiser ver meus Tweets, uso o site regular do Twitters. Eu não realizo um REST GET com sua API. Se eu quiser fazer algo programaticamente com meus tweets, utilizarei a API REST. Sim, as APIs devem ser compreensíveis, mas você
gte
não é tão ruim, apenas não é intuitivo.A outra coisa principal com o REST é que você poderá iniciar em qualquer ponto da sua API e navegar para todos os outros recursos associados, SEM saber o URL exato dos outros recursos antes do tempo. Os resultados do
GET
VERBO no REST devem retornar a URL REST completa dos recursos que referencia. Portanto, em vez de uma consulta retornar o ID de umPerson
objeto, ele retornaria a URL totalmente qualificada, comohttp://server/api/people/13
. Em seguida, você sempre pode navegar pelos resultados de forma programática, mesmo que o URL tenha sido alterado.Resposta ao comentário
No mundo real, de fato, existem coisas que precisam acontecer que não criam, leem, atualizam ou excluem (CRUD) um recurso.
Ações adicionais podem ser executadas em recursos. Os bancos de dados relacionais típicos suportam o conceito de procedimentos armazenados. Esses são comandos adicionais que podem ser executados em um conjunto de dados. O REST não possui esse conceito inerentemente. E não há razão para isso. Esses tipos de ações são perfeitos para serviços da Web RPC ou SOAP.
Esse é o problema geral que vejo nas APIs REST. Os desenvolvedores não gostam das limitações conceituais que cercam o REST e, portanto, o adaptam para fazer o que quiserem. Isso o impede de ser um serviço RESTful. Essencialmente, esses URLs são
GET
chamados para servlets do tipo pseudo-REST.Você tem poucas opções:
POST
ing dados adicionais para o recurso para executar uma ação.Se você usasse um parâmetro de consulta que VERB HTTP usaria para reenviar o email?
GET
- Isso reenvia o email E retorna os dados do recurso? E se um sistema armazenasse em cache esse URL e o tratasse como o URL exclusivo desse recurso. Toda vez que eles acessam o URL, ele reenvia um e-mail.POST
- Na verdade, você não enviou novos dados para o recurso, apenas um parâmetro de consulta adicional.Com base em todos os requisitos fornecidos, executar um
POST
no recurso comaction field
dados como POST resolverá o problema.fonte
Pergunta 1: É apropriado misturar algum tipo de chamada de ação com um URI de recurso [ou], seria melhor especificar a ação e passar o ID do recurso para ela?
Boa pergunta. Nesse caso, aconselho que você use a última abordagem, especifique a ação e passe um ID de recurso para ela. Dessa forma, quando seu recurso é modificado pela primeira vez, ele chama a
/sendEmail
ação (observação: não há necessidade de chamá-lo de "reenviar") como uma solicitação RESTful separada (que você pode chamar posteriormente repetidamente, independentemente do recurso que está sendo modificado )Pergunta 2: sobre como usar um operador de comparação da seguinte forma:
/collection?someField:gte=someval
Embora isso seja tecnicamente correto, provavelmente é uma má idéia. Um dos princípios principais do REST é a legibilidade. Eu sugiro que você simplesmente passe o operador de comparação como outro parâmetro, por exemplo:
/collection?someField=someval&operator=gte
e, é claro, projete sua API para atender a um caso padrão (caso ooperator
parâmetro seja deixado de fora do URI).Pergunta 3: Existe realmente uma boa razão para um URI REST ter mais de dois níveis de profundidade?
Sim; para abstração. Eu já vi algumas APIs REST que utilizam camadas de abstração através de vários níveis de URI, por exemplo:
/vehicles/cars/123
ou/vehicles/bikes/123
que, por sua vez, permitem que você trabalhe com informações úteis referentes a coleções/vehicles
e/vehicles/bikes
coleções. Dito isto, eu não sou um grande fã dessa abordagem; você raramente precisará fazer isso na prática e é provável que você possa redesenhar a API para usar apenas dois níveis.E sim, como sugerem os comentários acima, no futuro seria melhor dividir suas perguntas em postagens separadas;)
fonte
/collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq
.Para a pergunta 2, uma alternativa diferente pode ser mais flexível: considere cada pesquisa um recurso que o usuário cria antes de usar.
digamos que você tenha um contêiner de "pesquisas", lá você faz um
POST /api/searches/
com a especificação de consulta no conteúdo. pode ser um documento JSON, XML ou mesmo SQL, o que for mais fácil para você. Se a consulta analisar corretamente, uma nova pesquisa será criada como um novo recurso com seu próprio URI, digamos/api/searches/q123/
Em seguida, o cliente pode simplesmente
GET /api/searches/q123/
recuperar os resultados da consulta.Por fim, você pode solicitar ao cliente que exclua a consulta ou limpe-a após o fechamento da sessão.
fonte
Não, não é apropriado, pois os IRIs são para identificar recursos e não operações (no entanto, as pessoas usam essa abordagem de substituição de método por um tempo, nos casos em que o uso de métodos não POST e GET não é suportado). O que você pode fazer é procurar um método HTTP apropriado ou criar um novo. O POST pode ser seu amigo nesses casos (por exemplo, use-o se eles não encontrarem um método apropriado e a solicitação não for recuperada). Outra abordagem para gerar recursos com o envio de e-mails e, portanto,
POST /emails
pode enviar os e-mails sem criar um recurso real. Btw. A estrutura de URI não possui semântica, portanto, da perspectiva do REST, não importa realmente que tipo de URIs você usa. O que importa são os metadados (por exemplo, relação de link ) atribuídos aos links que você enviou aos clientes.Você não precisa criar uma linguagem de consulta própria. Prefiro usar um já existente e adicionar alguma descrição da consulta aos metadados do link. Você provavelmente deve usar um tipo de mídia RDF (por exemplo, JSON-LD) para fazer isso ou usar um tipo MIME personalizado (depois que não houver um formato não RDF que suporte isso). O uso de padrões existentes desacopla seu cliente do servidor, é disso que trata a restrição de interface uniforme.
Como mencionei anteriormente, a estrutura do URI não importa da perspectiva REST. Você poderia usar
/x71fd823df2
por exemplo. Ainda faria sentido para os clientes porque eles verificam os metadados atribuídos aos links e não à estrutura do URI. O principal objetivo do URI é identificar recursos. No padrão URI, eles afirmam que o caminho contém dados hierárquicos e a consulta contém dados não hierárquicos. Mas pode ser muito subjetivo o que é hierárquico. É por isso que você atende a vários níveis de URIs e URIs profundos com consultas longas também.Você deve ler pelo menos as restrições REST da dissertação Fielding , o padrão HTTP e provavelmente as APIs da web de terceira geração da Markus.
fonte