Primeiro de tudo, algumas definições:
PUT é definido na Seção 9.6 RFC 2616 :
O método PUT solicita que a entidade fechada seja armazenada no URI de solicitação fornecido. Se o pedido de URI refere-se a um recurso já existente, a entidade fechada deve ser considerada como uma versão modificada do que reside no servidor de origem . Se o Request-URI não apontar para um recurso existente e esse URI puder ser definido como um novo recurso pelo agente do usuário solicitante, o servidor de origem poderá criar o recurso com esse URI.
PATCH é definido na RFC 5789 :
O método PATCH solicita que um conjunto de alterações descrito na entidade solicitante seja aplicado ao recurso identificado pelo Request-URI.
Também de acordo com RFC 2616, a Seção 9.1.2 PUT é Idempotente enquanto a PATCH não.
Agora vamos dar uma olhada em um exemplo real. Quando eu POST /users
com os dados {username: 'skwee357', email: '[email protected]'}
e o servidor é capaz de criar um recurso, ele responde com 201 e a localização do recurso (vamos assumir /users/1
) e qualquer próxima chamada para GET /users/1
retornará {id: 1, username: 'skwee357', email: '[email protected]'}
.
Agora, digamos que quero modificar meu email. A modificação de e-mail é considerada "um conjunto de alterações" e, portanto, devo PATCH /users/1
com " documento de correção ". No meu caso, seria o documento JSON: {email: '[email protected]'}
. O servidor retorna 200 (assumindo que a permissão está ok). Isso me leva à primeira pergunta:
- PATCH NÃO é idempotente. Foi o que disse na RFC 2616 e na RFC 5789. No entanto, se eu emitir a mesma solicitação PATCH (com meu novo email), obterá o mesmo estado de recurso (com meu email sendo modificado para o valor solicitado). Por que PATCH não é idempotente?
PATCH é um verbo relativamente novo (RFC introduzido em março de 2010) e resolve o problema de "corrigir" ou modificar um conjunto de campos. Antes da introdução do PATCH, todos usavam o PUT para atualizar recursos. Mas depois que o PATCH foi introduzido, fiquei confuso sobre o uso do PUT. E isso me leva à minha segunda (e principal) pergunta:
- Qual é a diferença real entre PUT e PATCH? Eu li em algum lugar que PUT pode ser usado para substituir a entidade inteira sob um recurso específico, portanto, deve-se enviar a entidade completa (em vez de um conjunto de atributos como no PATCH). Qual é o uso prático real desse caso? Quando você deseja substituir / substituir uma entidade em um URI de recurso específico e por que essa operação não é considerada como atualizar / corrigir a entidade? O único caso de uso prático que vejo para PUT é emitir um PUT em uma coleção, ou seja,
/users
para substituir a coleção inteira. A emissão de PUT em uma entidade específica não faz sentido após a introdução do PATCH. Estou errado?
Respostas:
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.
Se você enviar este documento para
/users
, como sugere, poderá recuperar uma entidade comoSe você deseja modificar essa entidade posteriormente, escolha entre PUT e PATCH. Um PUT pode ficar assim:
Você pode realizar o mesmo usando PATCH. Isso pode ser assim:
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?
(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.
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.
Após um PATCH, você tem uma entidade modificada:
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.
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.
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.
fonte
GET /users/1
antes da Agência Postal atualizar o código postal e, em seguida, novamente fazer a mesmaGET /users/1
solicitação após a atualização da Agência Postal, você obteria duas respostas diferentes (CEP diferentes). O mesmo "A" (solicitação GET) está sendo exibido, mas você está obtendo resultados diferentes. No entanto, GET ainda é idempotente.Embora a excelente resposta de Dan Lowe tenha respondido minuciosamente à pergunta do OP sobre a diferença entre PUT e PATCH, sua resposta à pergunta de por que PATCH não é idempotente não está correta.
Para mostrar por que PATCH não é idempotente, é útil começar com a definição de idempotência (da Wikipedia ):
Em um idioma mais acessível, um PATCH idempotente pode ser definido como: Após PATCH de um recurso com um documento de correção, todas as chamadas PATCH subsequentes para o mesmo recurso com o mesmo documento de correção não mudarão o recurso.
Por outro lado, uma operação não idempotente é aquela em que f (f (x))! = F (x), que para PATCH pode ser declarada como: Após PATCH de um recurso com um documento de correção, PATCH subseqüente chama o mesmo recurso com o comando mesmo documento patch de fazer alterar o recurso.
Para ilustrar um PATCH não idempotente, suponha que haja um recurso / users e suponha que a chamada
GET /users
retorne uma lista de usuários, atualmente:Em vez de PATCHing / users / {id}, como no exemplo do OP, suponha que o servidor permita PATCHing / users. Vamos emitir esta solicitação PATCH:
Nosso documento de correção instrui o servidor a adicionar um novo usuário chamado
newuser
à lista de usuários. Depois de chamar isso pela primeira vez,GET /users
retornaria:Agora, se emitirmos exatamente a mesma solicitação de PATCH acima, o que acontece? (Para fins de exemplo, vamos supor que o recurso / users permita nomes de usuário duplicados.) O "op" é "add", para que um novo usuário seja adicionado à lista e
GET /users
retorne posteriormente :O recurso / users mudou novamente , apesar de termos emitido exatamente o mesmo PATCH no mesmo ponto final. Se nosso PATCH é f (x), f (f (x)) não é o mesmo que f (x) e, portanto, esse PATCH em particular não é idempotente .
Embora não seja garantido que PATCH seja idempotente, não há nada na especificação PATCH para impedir que você faça todas as operações PATCH em seu servidor idempotente. O RFC 5789 antecipa vantagens de solicitações idempotentes de PATCH:
No exemplo de Dan, sua operação PATCH é, de fato, idempotente. Nesse exemplo, a entidade / users / 1 mudou entre nossas solicitações PATCH, mas não por causa das solicitações PATCH; na verdade, foi o documento de correção diferente da agência postal que causou a alteração do código postal. O PATCH diferente dos Correios é uma operação diferente; se o nosso PATCH for f (x), o PATCH da agência postal é g (x). Idempotência afirma isso
f(f(f(x))) = f(x)
, mas não garantef(g(f(x)))
.fonte
/users
, isso também tornaria PUT não idempotente. Tudo isso se resume a como o servidor foi projetado para lidar com solicitações./users
), qualquer solicitação de PUT deverá substituir o conteúdo dessa coleção. Portanto, um PUT/users
deve esperar uma coleção de usuários e excluir todos os outros. Isso é idempotente. Não é provável que você faça isso no terminal / users. Mas algo como/users/1/emails
pode ser uma coleção e pode ser perfeitamente válido para permitir substituir a coleção inteira por uma nova.op
ação adicional que está acionando uma lógica específica do lado do servidor. Isso exigiria que o servidor e o cliente estivessem cientes dos valores específicos a serem passados para oop
campo para acionar fluxos de trabalho do lado do servidor. Em cenários REST mais diretos, esse tipo deop
funcionalidade é uma prática recomendada e provavelmente deve ser tratada diretamente através de verbos HTTP.Também fiquei curioso e encontrei alguns artigos interessantes. Talvez eu não responda sua pergunta em toda a extensão, mas isso pelo menos fornece mais algumas informações.
http://restful-api-design.readthedocs.org/en/latest/methods.html
Dado isso, um PUT deve enviar o objeto inteiro. Por exemplo,
Isso atualizaria efetivamente o email. A razão pela qual o PUT pode não ser muito eficaz é que você só modifica realmente um campo e inclui o nome de usuário é inútil. O próximo exemplo mostra a diferença.
Agora, se o PUT foi projetado de acordo com as especificações, o PUT definiria o nome de usuário como nulo e você obteria o seguinte.
Quando você usa um PATCH, atualiza apenas o campo especificado e deixa o resto em paz, como no seu exemplo.
A opinião a seguir sobre o PATCH é um pouco diferente do que nunca vi antes.
http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
Você está tratando mais ou menos o PATCH como uma maneira de atualizar um campo. Então, em vez de enviar o objeto parcial, você está enviando a operação. ou seja, substitua o email por valor.
O artigo termina com isso.
Agora, não sei se concordo particularmente com o artigo, como muitos comentaristas apontam. O envio de uma representação parcial pode ser facilmente uma descrição das alterações.
Para mim, eu sou misto em usar PATCH. Na maioria das vezes, tratarei PUT como um PATCH, pois a única diferença real que notei até agora é que PUT "deve" definir valores ausentes como nulos. Pode não ser a maneira "mais correta" de fazer isso, mas a boa sorte é a codificação perfeita.
fonte
A diferença entre PUT e PATCH é que:
PATCH requer alguma "linguagem de correção" para informar ao servidor como modificar o recurso. O chamador e o servidor precisam definir algumas "operações" como "adicionar", "substituir" e "excluir". Por exemplo:
Em vez de usar campos explícitos de "operação", a linguagem do patch pode torná-la implícita, definindo convenções como:
no corpo da solicitação PATCH:
Com a convenção acima, o PATCH no exemplo pode assumir o seguinte formato:
O que parece mais conciso e fácil de usar. Mas os usuários precisam estar cientes da convenção subjacente.
Com as operações mencionadas acima, o PATCH ainda é idempotente. Mas se você definir operações como: "incremento" ou "anexar", poderá ver facilmente que não será mais idempotente.
fonte
TLDR - Versão Dumbed Down
PUT => Defina todos os novos atributos para um recurso existente.
PATCH => Atualize parcialmente um recurso existente (nem todos os atributos são necessários).
fonte
Deixe-me citar e comentar mais de perto a seção 4.2.2 da RFC 7231 , já citada em comentários anteriores:
Então, o que deve ser "o mesmo" após uma solicitação repetida de um método idempotente? Não é o estado do servidor, nem a resposta do servidor, mas o efeito pretendido . Em particular, o método deve ser idempotente "do ponto de vista do cliente". Agora, acho que esse ponto de vista mostra que o último exemplo da resposta de Dan Lowe , que não quero plagiar aqui, mostra que uma solicitação PATCH pode ser não-idempotente (de uma maneira mais natural que o exemplo em Resposta de Jason Hoetger ).
De fato, vamos tornar o exemplo um pouco mais preciso, tornando possível uma intenção explícita possível para o primeiro cliente. Digamos que esse cliente percorra a lista de usuários do projeto para verificar seus e- mails e códigos postais. Ele começa com o usuário 1, percebe que o zip está correto, mas o email está errado. Ele decide corrigir isso com uma solicitação PATCH, que é totalmente legítima, e envia apenas
já que esta é a única correção. Agora, a solicitação falha devido a algum problema de rede e é reenviada automaticamente algumas horas depois. Enquanto isso, outro cliente (erroneamente) modificou o zip do usuário 1. Em seguida, o envio da mesma solicitação PATCH pela segunda vez não atinge o efeito pretendido do cliente, pois acabamos com um zip incorreto. Portanto, o método não é idempotente no sentido da RFC.
Se, em vez disso, o cliente usar uma solicitação PUT para corrigir o email, enviando ao servidor todas as propriedades do usuário 1 junto com o email, seu efeito pretendido será alcançado, mesmo que a solicitação precise ser reenviada mais tarde e o usuário 1 tenha sido modificado enquanto isso --- desde que a segunda solicitação PUT substitua todas as alterações desde a primeira solicitação.
fonte
Na minha humilde opinião, idempotência significa:
Eu envio uma definição de recurso de competição, então - o estado do recurso resultante é exatamente como definido pelos parâmetros de PUT. Sempre que atualizo o recurso com os mesmos parâmetros PUT - o estado resultante é exatamente o mesmo.
Enviei apenas parte da definição de recurso, portanto, outros usuários estão atualizando os OUTROS parâmetros desse recurso nesse meio tempo. Consequentemente - correções consecutivas com os mesmos parâmetros e seus valores podem resultar em diferentes estados de recursos. Por exemplo:
Suponha um objeto definido da seguinte maneira:
CARRO: - cor: preto, - tipo: sedan, - assentos: 5
Eu o remendo com:
{cor vermelha'}
O objeto resultante é:
CARRO: - cor: vermelho, - tipo: sedan, - assentos: 5
Em seguida, alguns outros usuários corrigem este carro com:
{type: 'hatchback'}
portanto, o objeto resultante é:
CARRO: - cor: vermelho, - tipo: hatchback, - assentos: 5
Agora, se eu corrigir esse objeto novamente com:
{cor vermelha'}
o objeto resultante é:
CARRO: - cor: vermelho, - tipo: hatchback, - assentos: 5
O que é DIFERENTE para o que eu tenho anteriormente!
É por isso que PATCH não é idempotente enquanto PUT é idempotente.
fonte
Para concluir a discussão sobre a idempotência, devo observar que é possível definir a idempotência no contexto REST de duas maneiras. Vamos formalizar primeiro algumas coisas:
Um recurso é uma função com seu codomain sendo a classe de strings. Em outras palavras, um recurso é um subconjunto de
String × Any
, onde todas as chaves são únicas. Vamos chamar a classe dos recursosRes
.Uma operação REST em recursos é uma função
f(x: Res, y: Res): Res
. Dois exemplos de operações REST são:PUT(x: Res, y: Res): Res = x
ePATCH(x: Res, y: Res): Res
, que funciona comoPATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}
.(Essa definição é projetada especificamente para discutir sobre
PUT
ePOST
, por exemplo, não faz muito sentidoGET
ePOST
, pois não se preocupa com persistência).Agora, corrigindo
x: Res
(falando de forma informativa, usando currying)PUT(x: Res)
ePATCH(x: Res)
são funções univariadas do tipoRes → Res
.A função
g: Res → Res
é chamada globalmente idempotentes , quandog ○ g == g
, ou seja, para qualquery: Res
,g(g(y)) = g(y)
.Deixe
x: Res
um recurso ek = x.keys
. Uma funçãog = f(x)
é chamada idempotente à esquerda , quando para cada umay: Res
temosg(g(y))|ₖ == g(y)|ₖ
. Basicamente, significa que o resultado deve ser o mesmo, se observarmos as chaves aplicadas.Portanto,
PATCH(x)
não é globalmente idempotente, mas é deixado idempotente. E a idempotência à esquerda é o que importa aqui: se corrigirmos algumas chaves do recurso, queremos que essas chaves sejam as mesmas se corrigirmos novamente e não nos importamos com o restante do recurso.E quando a RFC está falando sobre o PATCH não ser idempotente, está falando sobre a idempotência global. Bem, é bom que não seja globalmente idempotente, caso contrário, teria sido uma operação interrompida.
Agora, a resposta de Jason Hoetger está tentando demonstrar que PATCH nem sequer é deixado idempotente, mas está quebrando muitas coisas para fazer isso:
t: Set<T> → Map<T, Boolean>
definida comx in A iff t(A)(x) == True
. Usando essa definição, o patch é deixado como idempotente.{id: 1, email: "[email protected]"}
deve corresponder a{email: "[email protected]"}
, caso contrário, o programa estará sempre quebrado e o PATCH não poderá fragmento). Se o ID for gerado antes da verificação no conjunto, novamente o programa será interrompido.Pode-se fazer exemplos de PUT como não-idempotente com a quebra de metade das coisas que são quebradas neste exemplo:
PUT /user/12 {email: "[email protected]"}
resulta na{email: "...", version: 1}
primeira e{email: "...", version: 2}
na segunda vez.Todos os exemplos acima são exemplos naturais que podemos encontrar.
Meu ponto final é que o PATCH não deve ser globalmente idempotente , caso contrário não dará o efeito desejado. Você deseja alterar o endereço de email do seu usuário, sem tocar no restante das informações, e não deseja substituir as alterações de outra parte que acessa o mesmo recurso.
fonte
Uma informação adicional que eu apenas devo acrescentar é que uma solicitação PATCH usa menos largura de banda em comparação com uma solicitação PUT, uma vez que apenas uma parte dos dados é enviada e não toda a entidade. Portanto, basta usar uma solicitação PATCH para atualizações de registros específicos, como (1-3 registros), enquanto a solicitação PUT para atualizar uma quantidade maior de dados. É isso, não pense muito ou se preocupe demais.
fonte