NOTA : Quando passei um tempo lendo sobre o REST, a idempotência era um conceito confuso para tentar acertar. Ainda não entendi direito na minha resposta original, como outros comentários (e a resposta de Jason Hoetger ) mostraram. Por um tempo, resisti a atualizar essa resposta extensivamente, para evitar plagiar efetivamente o Jason, mas estou editando agora porque, bem, me pediram (nos comentários).
Depois de ler minha resposta, sugiro que você também leia a excelente resposta de Jason Hoetger para essa pergunta, e tentarei melhorar minha resposta sem simplesmente roubar de Jason.
Por que o PUT é idempotente?
Como você observou na sua citação na RFC 2616, a PUT é considerada idempotente. Quando você coloca um recurso, essas duas suposições estão em jogo:
Você está se referindo a uma entidade, não a uma coleção.
A entidade que você está fornecendo está completa (a entidade inteira ).
Vejamos um de seus exemplos.
{ "username": "skwee357", "email": "skwee357@domain.com" }
Se você enviar este documento para /users
, como sugere, poderá recuperar uma entidade como
## /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
Se você deseja modificar essa entidade posteriormente, escolha entre PUT e PATCH. Um PUT pode ficar assim:
PUT /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // new email address
}
Você pode realizar o mesmo usando PATCH. Isso pode ser assim:
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
Você notará uma diferença imediatamente entre esses dois. O PUT incluía todos os parâmetros desse usuário, mas o PATCH incluía apenas aquele que estava sendo modificado ( email
).
Ao usar PUT, supõe-se que você esteja enviando a entidade completa, e essa entidade completa substitua qualquer entidade existente nesse URI. No exemplo acima, o PUT e o PATCH alcançam o mesmo objetivo: ambos alteram o endereço de e-mail desse usuário. Mas o PUT lida com isso substituindo toda a entidade, enquanto PATCH apenas atualiza os campos que foram fornecidos, deixando os outros em paz.
Como as solicitações de PUT incluem a entidade inteira, se você emitir a mesma solicitação repetidamente, sempre deve ter o mesmo resultado (os dados que você enviou agora são os dados completos da entidade). Portanto, PUT é idempotente.
Usando PUT errado
O que acontece se você usar os dados PATCH acima em uma solicitação PUT?
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
PUT /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"email": "skwee357@gmail.com" // new email address... and nothing else!
}
(Para os propósitos desta pergunta, estou assumindo que o servidor não possui nenhum campo obrigatório específico e permitiria que isso acontecesse ... isso pode não ser o caso na realidade.)
Desde que usamos PUT, mas apenas fornecemos email
, agora essa é a única coisa nessa entidade. Isso resultou na perda de dados.
Este exemplo está aqui para fins ilustrativos - nunca faça isso de fato. Essa solicitação PUT é tecnicamente idempotente, mas isso não significa que não seja uma idéia terrível e quebrada.
Como PATCH pode ser idempotente?
No exemplo acima, PATCH era idempotente. Você fez uma alteração, mas se fizesse a mesma alteração repetidamente, sempre retornaria o mesmo resultado: você alterou o endereço de email para o novo valor.
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // email address was changed
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address... again
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // nothing changed since last GET
}
Meu exemplo original, corrigido para precisão
Originalmente, tive exemplos que achei que mostravam não-idempotência, mas eram enganosos / incorretos. Vou manter os exemplos, mas usá-los para ilustrar uma coisa diferente: que vários documentos PATCH contra a mesma entidade, modificando atributos diferentes, não tornam os PATCHes não idempotentes.
Digamos que em algum momento passado, um usuário foi adicionado. Este é o estado em que você está começando.
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@olddomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
Após um PATCH, você tem uma entidade modificada:
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com", // the email changed, yay!
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
Se você aplicar repetidamente seu PATCH, continuará obtendo o mesmo resultado: o email foi alterado para o novo valor. A entra, A sai; portanto, isso é idempotente.
Uma hora depois, depois de tomar um café e fazer uma pausa, alguém vem junto com seu próprio PATCH. Parece que os Correios estão fazendo algumas mudanças.
PATCH /users/1
{"zip": "12345"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com", // still the new email you set
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345" // and this change as well
}
Como esse PATCH da agência postal não se preocupa com o e-mail, apenas o código postal, se for aplicado repetidamente, também terá o mesmo resultado: o CEP está definido com o novo valor. A entra, A sai; portanto, isso também é idempotente.
No dia seguinte, você decide enviar seu PATCH novamente.
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345"
}
Seu patch tem o mesmo efeito que teve ontem: definiu o endereço de email. A entrou, A saiu, portanto isso também é idempotente.
O que eu errei na minha resposta original
Quero fazer uma distinção importante (algo que eu errei na minha resposta original). Muitos servidores responderão às suas solicitações REST enviando de volta o novo estado da entidade, com suas modificações (se houver). Portanto, quando você recebe essa resposta de volta, ela é diferente daquela que recebeu ontem , porque o CEP não é o que você recebeu da última vez. No entanto, sua solicitação não estava relacionada ao CEP, apenas ao e-mail. Portanto, seu documento PATCH ainda é idempotente - o email que você enviou no PATCH agora é o endereço de email na entidade.
Então, quando PATCH não é idempotente, então?
Para um tratamento completo desta questão, refiro-lhe novamente à resposta de Jason Hoetger . Só vou deixar assim, porque sinceramente não acho que posso responder melhor a essa parte do que ele já tem.