Por que apontar para um design RESTful?
Os princípios RESTful trazem os recursos que tornam os sites fáceis (para um usuário humano aleatório "navegá-los") no design da API de serviços da web , para que eles sejam fáceis de usar por um programador. REST não é bom porque é REST, é bom porque é bom. E é bom principalmente porque é simples .
A simplicidade do HTTP simples (sem envelopes SOAP e POST
serviços sobrecarregados com URI único ), o que alguns podem chamar de "falta de recursos" , é na verdade sua maior força . Logo de cara, o HTTP pede que você tenha capacidade de endereçamento e ausência de estado : as duas decisões básicas de design que mantêm o HTTP escalável até os mega-sites de hoje (e mega-serviços).
Mas o REST não é o marcador de prata: às vezes um estilo RPC ("Chamada de Procedimento Remoto" - como SOAP) pode ser apropriado e, outras vezes, outras necessidades têm precedência sobre as virtudes da Web. Isto é bom. O que realmente não gostamos é de complexidade desnecessária . Muitas vezes, um programador ou uma empresa traz serviços no estilo RPC para um trabalho que o HTTP antigo simples poderia suportar. O efeito é que o HTTP é reduzido a um protocolo de transporte para uma enorme carga XML que explica o que "realmente" está acontecendo (não o URI ou o método HTTP dá uma pista sobre isso). O serviço resultante é muito complexo, impossível de depurar e não funcionará, a menos que seus clientes tenham a configuração exata que o desenvolvedor pretendia.
Mesma forma que um código Java / C # pode ser não orientada a objetos, usando apenas HTTP não faz um design RESTful. Pode-se ficar com a pressa de pensar em seus serviços em termos de ações e métodos remotos que devem ser chamados. Não é de admirar que isso acabe principalmente em um serviço no estilo RPC (ou em um híbrido REST-RPC). O primeiro passo é pensar de forma diferente. Um design RESTful pode ser alcançado de várias maneiras, uma maneira é pensar em seu aplicativo em termos de recursos, não ações:
💡 Em vez de pensar em termos de ações, ele pode executar ("faça uma busca por lugares no mapa") ...
... tente pensar em termos dos resultados dessas ações ("a lista de lugares no mapa que corresponde aos critérios de pesquisa").
Vou dar exemplos abaixo. (Outro aspecto importante do REST é o uso do HATEOAS - não o escovo aqui, mas falo sobre isso rapidamente em outro post .)
Edições do primeiro desenho
Vamos dar uma olhada no design proposto:
ACTION http://api.animals.com/v1/dogs/1/
Primeiro, não devemos considerar a criação de um novo verbo HTTP ( ACTION
). De um modo geral, isso é indesejável por vários motivos:
- (1) Dado apenas o URI de serviço, como um programador "aleatório" saberá que o
ACTION
verbo existe?
- (2) se o programador sabe que existe, como ele saberá sua semântica? O que esse verbo significa?
- (3) que propriedades (segurança, idempotência) devemos esperar que esse verbo tenha?
- (4) e se o programador tiver um cliente muito simples que lide apenas com verbos HTTP padrão?
- (5) ...
Agora vamos considerar o usoPOST
(discutirei o porquê abaixo, basta aceitar minha palavra agora):
POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
{"action":"bark"}
Isso pode ser bom ... mas somente se :
{"action":"bark"}
era um documento; e
/v1/dogs/1/
era um URI de "processador de documentos" (semelhante à fábrica). Um "processador de documentos" é um URI no qual você simplesmente "joga as coisas" e "esquece" delas - o processador pode redirecioná-lo para um recurso recém-criado após a "execução". Por exemplo, o URI para postar mensagens em um serviço de intermediário de mensagens, que após a postagem o redirecionaria para um URI que mostra o status do processamento da mensagem.
Não sei muito sobre o seu sistema, mas já aposto que os dois não são verdadeiros:
{"action":"bark"}
não é um documento , na verdade é o método que você está tentando invadir o serviço; e
- o
/v1/dogs/1/
URI representa um recurso "cão" (provavelmente o cão com ele id==1
) e não um processador de documentos.
Então, tudo o que sabemos agora é que o design acima não é tão RESTful, mas o que é isso exatamente? O que há de tão ruim nisso? Basicamente, é ruim porque é um URI complexo com significados complexos. Você não pode deduzir nada disso. Como um programador saberia que um cachorro tem uma bark
ação que pode ser secretamente infundida com uma ação POST
nele?
Criando chamadas de API da sua pergunta
Então, vamos direto ao ponto e tentamos projetar esses latidos RESTfully pensando em termos de recursos . Permitam-me citar o livro Restful Web Services :
Uma POST
solicitação é uma tentativa de criar um novo recurso a partir de um existente. O recurso existente pode ser o pai do novo em um sentido de estrutura de dados, da maneira como a raiz de uma árvore é o pai de todos os seus nós de folha. Ou o recurso existente pode ser um recurso especial de "fábrica"
cujo único objetivo é gerar outros recursos. A representação enviada junto com uma POST
solicitação descreve o estado inicial do novo recurso. Como no PUT, uma POST
solicitação não precisa incluir nenhuma representação.
Seguindo a descrição acima, podemos ver que bark
pode ser modelado como uma sub-fonte de adog
( uma vez que a bark
está contida em um cachorro, ou seja, uma casca é "latida" por um cachorro).
A partir desse raciocínio, já obtivemos:
- O método é
POST
- O recurso é
/barks
, sub-fonte de dog:, /v1/dogs/1/barks
representando uma bark
"fábrica". Esse URI é único para cada cão (já que está abaixo /v1/dogs/{id}
).
Agora, cada caso da sua lista tem um comportamento específico.
1. o latido apenas envia um e-mail para dog.email
e não registra nada.
Em primeiro lugar, latir (enviar um email) é uma tarefa síncrona ou assíncrona? Em segundo lugar, a bark
solicitação requer algum documento (o e-mail, talvez) ou está vazio?
1.1 casca envia um e-mail para dog.email
e não registra nada (como uma tarefa síncrona)
Este caso é simples. Uma chamada para o barks
recurso de fábrica produz uma casca (um e-mail enviado) imediatamente e a resposta (se OK ou não) é dada imediatamente:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(entity-body is empty - or, if you require a **document**, place it here)
200 OK
Como ele registra (muda) nada, 200 OK
é suficiente. Isso mostra que tudo correu como esperado.
1.2 casca envia um e-mail para dog.email
e não registra nada (como uma tarefa assíncrona)
Nesse caso, o cliente deve ter uma maneira de rastrear a bark
tarefa. A bark
tarefa deve ser um recurso com seu próprio URI .:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Dessa forma, cada um bark
é rastreável. O cliente pode então emitir um GET
no bark
URI para saber seu estado atual. Talvez até use a DELETE
para cancelar.
2. casca envia um e-mail para dog.email
e depois incrementa dog.barkCount
em 1
Essa pode ser mais complicada, se você quiser que o cliente saiba que o dog
recurso foi alterado:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}
303 See Other
Location: http://api.animals.com/v1/dogs/1
Nesse caso, a location
intenção do cabeçalho é informar ao cliente que ele deve dar uma olhada dog
. Do HTTP RFC sobre303
:
Esse método existe principalmente para permitir que a saída de um
POST
script ativado redirecione o agente do usuário para um recurso selecionado.
Se a tarefa for assíncrona, bark
é necessário um sub-recurso, exatamente como a 1.2
situação, e 303
deve ser retornado GET .../barks/Y
quando a tarefa estiver concluída.
3. casca cria um novo " bark
" registro com a bark.timestamp
gravação quando a casca ocorreu. Também aumenta dog.barkCount
em 1.
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Aqui, bark
é criado devido à solicitação, portanto o status 201 Created
é aplicado.
Se a criação for assíncrona, 202 Accepted
será necessário um ( como o HTTP RFC diz ).
O registro de data e hora salvo faz parte do bark
recurso e pode ser recuperado com um GET
a ele. O cão atualizado também pode ser "documentado" GET dogs/X/barks/Y
.
4. bark executa um comando do sistema para retirar a versão mais recente do código do cão do Github. Em seguida, ele envia uma mensagem de texto dog.owner
informando que o novo código de cão está em produção.
A redação deste é complicada, mas é praticamente uma tarefa assíncrona simples:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
O cliente então emitia GET
s para /v1/dogs/1/barks/a65h44
saber o estado atual (se o código foi extraído, o e-mail foi enviado ao proprietário e assim por diante). Sempre que o cão muda, a 303
é aplicável.
Empacotando
Citando Roy Fielding :
A única coisa que o REST exige dos métodos é que eles sejam definidos uniformemente para todos os recursos (ou seja, para que os intermediários não precisem conhecer o tipo de recurso para entender o significado da solicitação).
Nos exemplos acima, POST
é projetado uniformemente. Isso fará o cachorro " bark
". Isso não é seguro (significando que a casca tem efeitos sobre os recursos), nem idempotente (cada solicitação produz uma nova bark
), que se encaixa POST
bem no verbo.
Um programador saberia: POST
a barks
rende a bark
. Os códigos de status de resposta (também com o corpo da entidade e os cabeçalhos quando necessário) explicam o que mudou e como o cliente pode e deve proceder.
Nota: As principais fontes usadas foram: livro " Restful Web Services ", o HTTP RFC e o blog de Roy Fielding .
Editar:
A pergunta e, portanto, a resposta mudaram bastante desde que foram criadas. A pergunta original perguntou sobre o design de um URI como:
ACTION http://api.animals.com/v1/dogs/1/?action=bark
Abaixo está a explicação de por que não é uma boa escolha:
Como os clientes dizem ao servidor O QUE FAZER com os dados são as informações do método .
- Os serviços da web RESTful transmitem informações sobre o método no método HTTP.
- Os serviços típicos de estilo RPC e SOAP mantêm os deles no corpo da entidade e no cabeçalho HTTP.
Em que parte dos dados [o cliente deseja que o servidor] funcione estão as informações de escopo .
- Serviços RESTful usam o URI. Os serviços no estilo SOAP / RPC usam mais uma vez o corpo da entidade e os cabeçalhos HTTP.
Como exemplo, use o URI do Google http://www.google.com/search?q=DOG
. Lá, as informações do método são GET
e as informações do escopo são /search?q=DOG
.
Longa história curta:
- Nas arquiteturas RESTful , as informações do método entram no método HTTP.
- Em Arquiteturas Orientadas a Recursos , as informações do escopo são inseridas no URI.
E a regra de ouro:
Se o método HTTP não corresponder às informações do método, o serviço não é RESTful. Se as informações do escopo não estiverem no URI, o serviço não será orientado a recursos.
Você pode colocar a ação "latir" na URL (ou no corpo da entidade) e usar . Não tem problema, ele funciona e pode ser a maneira mais simples de fazer isso, mas isso não é RESTful .POST
Para manter seu serviço realmente RESTful, pode ser necessário dar um passo atrás e pensar no que você realmente deseja fazer aqui (que efeitos isso terá sobre os recursos).
Não posso falar sobre suas necessidades comerciais específicas, mas deixe-me dar um exemplo: considere um serviço de pedidos RESTful em que os pedidos são em URIs example.com/order/123
.
Agora diga que queremos cancelar um pedido, como vamos fazer isso? Pode-se sentir tentado a pensar que é uma "ação" de "cancelamento " e designá-la como POST example.com/order/123?do=cancel
.
Isso não é RESTful, como falamos acima. Em vez disso, poderíamos PUT
uma nova representação do order
com um canceled
elemento enviado para true
:
PUT /order/123 HTTP/1.1
Content-Type: application/xml
<order id="123">
<customer id="89987">...</customer>
<canceled>true</canceled>
...
</order>
E é isso. Se o pedido não puder ser cancelado, um código de status específico poderá ser retornado. (Um design de sub-fonte, como POST /order/123/canceled
o corpo da entidade true
, também pode estar disponível por simplicidade.)
No seu cenário específico, você pode tentar algo semelhante. Dessa forma, enquanto um cachorro está latindo, por exemplo, um GET
at /v1/dogs/1/
pode incluir essa informação (por exemplo <barking>true</barking>
) . Ou ... se isso for muito complicado, afrouxe seu requisito RESTful e atenha-se POST
.
Atualizar:
Não quero tornar a resposta muito grande, mas demora um pouco para expor um algoritmo (uma ação ) como um conjunto de recursos. Em vez de pensar em termos de ações ( "faça uma pesquisa por locais no mapa" ), é preciso pensar em termos dos resultados dessa ação ( "a lista de locais no mapa que corresponde aos critérios de pesquisa" ).
Você pode voltar a esta etapa se achar que seu design não se encaixa na interface uniforme do HTTP.
As variáveis de consulta são informações de escopo , mas não denotam novos recursos ( /post?lang=en
é claramente o mesmo recurso que /post?lang=jp
, apenas uma representação diferente). Em vez disso, eles são usados para transmitir o estado do cliente (como ?page=10
, para que o estado não seja mantido no servidor; ?lang=en
também é um exemplo aqui) ou parâmetros de entrada para recursos algorítmicos ( /search?q=dogs
, /dogs?code=1
). Mais uma vez, não recursos distintos.
Propriedades dos métodos dos verbos HTTP:
Outro ponto claro que mostra ?action=something
no URI não é RESTful, são as propriedades dos verbos HTTP:
GET
e HEAD
são seguros (e idempotentes);
PUT
e DELETE
são apenas idempotentes;
POST
não é nenhum.
Segurança : Um GET
ou HEAD
pedido é um pedido para ler alguns dados, não é um pedido para alterar qualquer estado do servidor. O cliente pode fazer GET
ou HEAD
solicitar 10 vezes e é o mesmo que fazê-lo uma vez ou nunca fazê-lo .
Idempotência : uma operação idempotente em uma que tenha o mesmo efeito, seja aplicada uma ou mais de uma vez (em matemática, multiplicar por zero é idempotente). Se você DELETE
usar um recurso uma vez, a exclusão novamente terá o mesmo efeito (o recurso GONE
já está ).
POST
não é seguro nem idempotente. Fazer duas POST
solicitações idênticas a um recurso 'factory' provavelmente resultará em dois recursos subordinados contendo as mesmas informações. Com sobrecarregado (método no URI ou no corpo da entidade) POST
, todas as apostas estão desativadas.
Ambas as propriedades foram importantes para o sucesso do protocolo HTTP (em redes não confiáveis!): Quantas vezes você atualizou ( GET
) a página sem esperar até que ela esteja totalmente carregada?
Criar uma ação e colocá-la na URL quebra claramente o contrato dos métodos HTTP. Mais uma vez, a tecnologia permite, você pode fazê-lo, mas esse não é o design RESTful.