Operações não CRUD em um serviço RESTful

106

Qual é a maneira "RESTful" de adicionar operações não CRUD a um serviço RESTful? Digamos que eu tenha um serviço que permite o acesso CRUD a registros como este:

GET /api/car/123           <- Returns information for the Car object with ID 123
POST /api/car              <- Creates a new car (with properties in the request)
PUT /api/car/123           <- Updates car 123 (with properties in the request)
DELETE /api/car/123        <- Deletes car 123    
POST /api/car/123/wheel/   <- Creates a wheel and associates it to car 123

Se eu quiser mudar a cor do carro, eu simplesmente POST /api/car/123 incluiria uma variável POST para a nova cor.

Mas digamos que eu queira comprar um carro e essa operação seja mais complicada do que simplesmente atualizar a propriedade de "carro de propriedade" de um registro do "usuário". É RESTful simplesmente fazer algo como POST /api/car/123/purchase, onde "compra" é essencialmente um nome de método? Ou devo usar um verbo HTTP personalizado, como em PURCHASEvez dePOST ?

Ou as operações não CRUD estão completamente fora do escopo do REST?

MikeWyatt
fonte
5
Se você estiver mudando a cor de um carro, seria melhor usar PATCH /api/car/123e enviar um parâmetro de cor OU usar PUT /api/car/123e enviar o objeto carro inteiro. O POST inferiria que você está criando um novo carro e provavelmente nunca deveria incluir um id no final do URL
RonnyKnoxville

Respostas:

65

Pense na compra como uma entidade comercial ou um recurso no dicionário RESTful. Dito isso, fazer uma compra é, na verdade, criar um novo recurso. Assim:

POST /api/purchase

fará um novo pedido. Os dados (usuário, carro, etc.) devem ser referenciados por id (ou URI) dentro do conteúdo enviado para este endereço.

Não importa que encomendar um carro não seja apenas um simples INSERT no banco de dados. Na verdade, REST não é expor suas tabelas de banco de dados como operações CRUD. Do ponto de vista lógico, você está criando um pedido (compra), mas o lado do servidor está livre para executar quantas etapas de processamento desejar.

Você pode até abusar ainda mais do protocolo HTTP. Use o Locationcabeçalho para retornar um link para o pedido recém-criado, escolha cuidadosamente os códigos de resposta HTTP para informar os usuários sobre os problemas (servidor ou cliente), etc.

Tomasz Nurkiewicz
fonte
3
REST trata da manipulação do estado dos recursos e todas as operações de negócios devem ser mapeadas para as operações CRUD do estado. Se você precisar de uma semântica de operações de negócios rígidas , terá que seguir o caminho do SOAP (SOAP é, na verdade, passagem de mensagens, mas normalmente é organizado em operações de solicitação-resposta).
Tomasz Nurkiewicz
23
O design de "compra como recurso" parece elegante. E se o recurso for uma "cerveja" .. e eu quiser que o garçom beba .. (foi para mim, com certeza eu PEGARIA;)) .. devemos considerar a "ação beber" como um recurso ?! .. ou "beber uma cerveja" é uma operação de negócio difícil ?! Mais a sério, o design RESTful é sobre considerar ações como recursos?! ..
Myobis
2
Como você exporia "aprovação de pedido de compra" por meio de um serviço REST? Acho que @TomaszNurkiewicz está certo ao dizer que tudo o que não pode ser feito ordenadamente de uma forma CRUD precisará da semântica de operação fornecida pelo SOAP. A menos que a "aprovação do pedido de compra" seja um modelo / entidade por conta própria. Por exemplo, POST / aprovação de PO (com detalhes de PO na solicitação).
mydoghasworms
2
Do ponto de vista de um cliente REST, "aprovar pedido de compra" deve ser apenas mais uma atualização do pedido. Por exemplo, altere "Aprovado" para "verdadeiro" e envie a atualização para o servidor. O servidor provavelmente precisará fazer várias verificações e provavelmente precisará atualizar / criar vários outros recursos. Mas esse é o problema dos servidores e não deve ser visível para o cliente.
AVee
2
@antinome: "Suponha que o cliente saiba um pouco disso", se for esse o caso, você não está fazendo REST (ainda pode ser um software válido e sensato!). REST foi desenhado para ser capaz de criar clientes que não sabem esse tipo de coisa, para criar clientes que ainda funcionam se o comportamento do servidor mudar. O que você está tentando fazer é o RPC clássico; você precisa revisar sua abordagem para que se encaixe no REST ou aceitar que está fazendo RPC e usar um protocolo destinado a RPC, como SOAP. REST tenta muito não ser RPC, então nunca será uma boa opção quando você quer / precisa de RPC.
AVee
15

Pelo que entendi, a maneira RESTful é que você não precisa de novos verbos HTTP, há um substantivo em algum lugar que significará o que você precisa fazer.

Comprar um carro? Bem não é isso

POST /api/order
djna
fonte
2
PUT não é usado para atualizar recursos por ser idempotente? Isso significa que você pode chamá-lo quantas vezes quiser, mas apenas a primeira / última chamada é importante. O POST, por outro lado, é usado para criar recursos e chamá-lo duas vezes deve, na verdade, criar dois.
Tomasz Nurkiewicz
1
@Tomas, sim, erro de digitação. Princípio é importante, porém, estamos lidando com uma coisa nova, uma ordem, sem necessidade de um novo verbo.
djna
5

O que você realmente está fazendo é criar um pedido. Portanto, adicione outro recurso para pedido e postagem e coloque lá durante o processo de pedido.

Pense em termos de recursos, em vez de chamadas de método.

Para finalizar o pedido, você provavelmente POSTAR / api / pedido // concluir ou algo semelhante.

Andrew Kothmann
fonte
3

Acho que as APIs REST ajudam de muito mais maneiras do que apenas fornecendo semântica. Portanto, não é possível escolher o estilo RPC apenas por causa de algumas chamadas que parecem fazer mais sentido no estilo de operação RPC. Exemplo é a API do google maps para encontrar direções entre dois lugares. Tem a seguinte aparência: http://maps.googleapis.com/maps/api/directions/json?origin=Jakkur&destination=Hebbal

Eles poderiam ter chamado de "findDirections" (verbo) e tratado como uma operação. Em vez disso, eles fizeram "direção" (substantivo) como um recurso e trataram a localização de direções como uma consulta no recurso de direções (embora internamente não pudesse haver nenhum recurso real chamado direção e ele pudesse ser implementado pela lógica de negócios para encontrar direções com base em parâmetros).

Maruthi
fonte
É um mau exemplo. Neste caso, as direções (todas as direções possíveis, número infinito delas) é o recurso e os parâmetros são apenas filtros. Mas você não pode fazer uma "compra" com isso, pois os filtros só fazem sentido em obter operações e colocar um pedido ou cancelá-lo são operações que alteram os dados
Tseng,
2
a compra seria POST para / pedido com um json no corpo para indicar que um pedido foi criado. cancel seria PUT to / order com um json carregando a mudança de estado do pedido para indicar que esta é uma atualização idempotente. Ainda estou para correr em uma operação que não pode ser expressa em um formato de recurso. Portanto, gostaria de ver um exemplo como esse
Maruthi