Uso dos métodos PUT vs PATCH nos cenários da vida real da API REST

680

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 /userscom 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/1retornará {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/1com " 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, /userspara 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?
Dmitry Kudryavtsev
fonte
1
a) é RFC 2616, não 2612. b) RFC 2616 foi obsoleto, a especificação atual de PUT está em greenbytes.de/tech/webdav/rfc7231.html#PUT , c) Não entendi sua pergunta; não é óbvio que PUT pode ser usado para substituir qualquer recurso, não apenas uma coleção; d) antes da introdução do PATCH, as pessoas usavam POST; e) finalmente, sim, uma solicitação PATCH específica (dependendo do formato do patch) pode ser idempotente; é que geralmente não é.
Julian Reschke
se isso ajuda eu escrevi um artigo sobre o patch vs PUT eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war
equivalent8
5
Simples: o POST cria um item em uma coleção. PUT substitui um item. PATCH modifica um item. Durante o POST, o URL do novo item é calculado e retornado na resposta, enquanto PUT e PATCH exigem um URL na solicitação. Direita?
Tom Russell
Esta publicação pode ser útil: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
micha

Respostas:

941

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:

  1. Você está se referindo a uma entidade, não a uma coleção.

  2. A entidade que você está fornecendo está completa (a entidade inteira ).

Vejamos um de seus exemplos.

{ "username": "skwee357", "email": "[email protected]" }

Se você enviar este documento para /users, como sugere, poderá recuperar uma entidade como

## /users/1

{
    "username": "skwee357",
    "email": "[email protected]"
}

Se você deseja modificar essa entidade posteriormente, escolha entre PUT e PATCH. Um PUT pode ficar assim:

PUT /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // new email address
}

Você pode realizar o mesmo usando PATCH. Isso pode ser assim:

PATCH /users/1
{
    "email": "[email protected]"       // 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": "[email protected]"
}
PUT /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "email": "[email protected]"      // 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": "[email protected]"
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // email address was changed
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // 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": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Após um PATCH, você tem uma entidade modificada:

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",    // 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": "[email protected]",  // 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": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "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.

Dan Lowe
fonte
2
Esta frase não está correta: "Mas é idempotente: sempre que A entra, B sempre sai". Por exemplo, se você fosse GET /users/1antes da Agência Postal atualizar o código postal e, em seguida, novamente fazer a mesma GET /users/1solicitaçã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.
Jason Hoetger
O @JasonHoetger GET é seguro (presumidamente não causa alterações), mas nem sempre é idempotente. Há uma diferença. Veja RFC 2616 seg. 9.1 .
Dan Lowe
1
@ DanLowe: GET é definitivamente garantido como idempotente. Ele diz exatamente que, na Seção 9.1.2 da RFC 2616, e na especificação atualizada, na seção 4.2.2 da RFC 7231 , "os métodos de solicitação definidos por esta especificação, PUT, DELETE e métodos de solicitação seguros são idempotentes". Idempotência simplesmente não significa "você recebe a mesma resposta toda vez que faz a mesma solicitação". 7231 4.2.2 continua dizendo: "Repetir a solicitação terá o mesmo efeito pretendido, mesmo que a solicitação original tenha sido bem-sucedida, embora a resposta possa ser diferente. "
Jason Hoetger 6/16
1
@JasonHoetger eu vou admitir isso, mas eu não vejo o que isso tem a ver com esta resposta, que discutiu PUT e PATCH e nunca sequer menciona GET ...
Dan Lowe
1
Ah, o comentário de @JasonHoetger esclareceu: apenas os estados resultantes, e não as respostas, de várias solicitações de métodos idempotentes precisam ser idênticos.
Tom Russell
328

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 ):

O termo idempotente é usado de forma mais abrangente para descrever uma operação que produzirá os mesmos resultados se executada uma ou várias vezes [...] Uma função idempotente é aquela que possui a propriedade f (f (x)) = f (x) para qualquer valor x.

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 /usersretorne uma lista de usuários, atualmente:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" }]

Em vez de PATCHing / users / {id}, como no exemplo do OP, suponha que o servidor permita PATCHing / users. Vamos emitir esta solicitação PATCH:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "[email protected]" }]

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 /usersretornaria:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" }]

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 /usersretorne posteriormente :

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" },
 { "id": 3, "username": "newuser", "email": "[email protected]" }]

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:

Uma solicitação PATCH pode ser emitida de forma a ser idempotente, o que também ajuda a evitar resultados ruins de colisões entre duas solicitações PATCH no mesmo recurso em um período de tempo semelhante.

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 garante f(g(f(x))).

Jason Hoetger
fonte
11
Supondo que o servidor também permita a emissão de PUT em /users, isso também tornaria PUT não idempotente. Tudo isso se resume a como o servidor foi projetado para lidar com solicitações.
Uzair Sajid
13
Portanto, poderíamos construir uma API apenas com operações PATCH. Então, o que se torna o princípio REST do uso de http VERBS para fazer ações CRUD nos Recursos? Não estamos complicando demais os cavalheiros das fronteiras do PATCH aqui?
bohr
6
Se o PUT for implementado em uma coleção (por exemplo /users), qualquer solicitação de PUT deverá substituir o conteúdo dessa coleção. Portanto, um PUT /usersdeve 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/emailspode ser uma coleção e pode ser perfeitamente válido para permitir substituir a coleção inteira por uma nova.
Vectorjohn
5
Embora esta resposta for um ótimo exemplo de idempotência, acredito que isso possa enlamear as águas em cenários típicos de REST. Nesse caso, você tem uma solicitação PATCH com uma opaçã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 o opcampo para acionar fluxos de trabalho do lado do servidor. Em cenários REST mais diretos, esse tipo de opfuncionalidade é uma prática recomendada e provavelmente deve ser tratada diretamente através de verbos HTTP.
21418 ivandov
7
Eu nunca consideraria emitir um PATCH, apenas POST e DELETE, contra uma coleção. Isso realmente é feito? PATCH pode, portanto, ser considerado idempotente para todos os fins práticos?
Tom Russell
72

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

O RFC HTTP especifica que o PUT deve ter uma nova representação completa de recursos como a entidade solicitante. Isso significa que se, por exemplo, apenas determinados atributos forem fornecidos, eles deverão ser removidos (ou seja, definidos como nulos).

Dado isso, um PUT deve enviar o objeto inteiro. Por exemplo,

/users/1
PUT {id: 1, username: 'skwee357', email: '[email protected]'}

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.

/users/1
PUT {id: 1, email: '[email protected]'}

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.

{id: 1, username: null, email: '[email protected]'}

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/

A diferença entre as solicitações PUT e PATCH se reflete na maneira como o servidor processa a entidade fechada para modificar o recurso identificado pelo Request-URI. Em uma solicitação PUT, a entidade incluída é considerada uma versão modificada do recurso armazenado no servidor de origem e o cliente está solicitando que a versão armazenada seja substituída. Com PATCH, no entanto, a entidade incluída contém um conjunto de instruções que descrevem como um recurso que atualmente reside no servidor de origem deve ser modificado para produzir uma nova versão. O método PATCH afeta o recurso identificado pelo Request-URI, e também PODE ter efeitos colaterais em outros recursos; ou seja, novos recursos podem ser criados, ou os existentes modificados, pela aplicação de um PATCH.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "[email protected]" }
]

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.

Vale ressaltar que PATCH não foi realmente projetado para APIs verdadeiramente REST, pois a dissertação de Fielding não define nenhuma maneira de modificar parcialmente os recursos. Mas o próprio Roy Fielding disse que PATCH foi algo [ele] criado para a proposta HTTP / 1.1 inicial porque PUT parcial nunca é RESTful. Claro que você não está transferindo uma representação completa, mas o REST não exige que as representações sejam concluídas de qualquer maneira.

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.

Kalel Wade
fonte
7
Vale a pena acrescentar: no artigo de William Durand (e na rfc 6902), existem exemplos em que "op" é "add". Obviamente, isso não é idempotente.
Johannes Brodwall 3/03
2
Ou você pode facilitar e usar o RFC 7396 Merge Patch e evitar a criação do patch JSON.
Piotr Kula
para tabelas NoSQL, as diferenças entre patch e put é importante, porque nosql não têm colunas
stackdave
18

A diferença entre PUT e PATCH é que:

  1. PUT é necessário para ser idempotente. Para conseguir isso, você deve colocar todo o recurso completo no corpo da solicitação.
  2. PATCH pode ser não-idempotente. O que implica que também pode ser idempotente em alguns casos, como nos casos que você descreveu.

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:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "[email protected]"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "address": "123 main street",
}

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:

  1. A existência de um campo significa "substituir" ou "adicionar" esse campo.
  2. Se o valor de um campo for nulo, isso significa excluir esse campo.

Com a convenção acima, o PATCH no exemplo pode assumir o seguinte formato:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "[email protected]",
  "zip":
}

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.

Bin Ni
fonte
7

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).

Bijan
fonte
3

Deixe-me citar e comentar mais de perto a seção 4.2.2 da RFC 7231 , já citada em comentários anteriores:

Um método de solicitação é considerado "idempotente" se o efeito pretendido no servidor de várias solicitações idênticas com esse método for o mesmo que o efeito de uma única solicitação. Dos métodos de solicitação definidos por esta especificação, PUT, DELETE e métodos de solicitação seguros são idempotentes.

(...)

Os métodos idempotentes são diferenciados porque a solicitação pode ser repetida automaticamente se ocorrer uma falha de comunicação antes que o cliente possa ler a resposta do servidor. Por exemplo, se um cliente enviar uma solicitação PUT e a conexão subjacente for fechada antes que qualquer resposta seja recebida, o cliente poderá estabelecer uma nova conexão e tentar novamente a solicitação idempotente. Ele sabe que repetir a solicitação terá o mesmo efeito pretendido, mesmo que a solicitação original tenha sido bem-sucedida, embora a resposta possa ser diferente.

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

PATCH /users/1
{"email": "[email protected]"}

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.

Rolvernew
fonte
2

Na minha humilde opinião, idempotência significa:

  • COLOCAR:

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.

  • FRAGMENTO:

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.

Zbigniew Szczęsny
fonte
1

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 recursos Res.

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 = xe
  • PATCH(x: Res, y: Res): Res, que funciona como PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Essa definição é projetada especificamente para discutir sobre PUTe POST, por exemplo, não faz muito sentido GETe POST, pois não se preocupa com persistência).

Agora, corrigindo x: Res(falando de forma informativa, usando currying) PUT(x: Res)e PATCH(x: Res)são funções univariadas do tipo Res → Res.

  1. A função g: Res → Resé chamada globalmente idempotentes , quando g ○ g == g, ou seja, para qualquer y: Res, g(g(y)) = g(y).

  2. Deixe x: Resum recurso e k = x.keys. Uma função g = f(x)é chamada idempotente à esquerda , quando para cada uma y: Restemos g(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:

  • Primeiro, PATCH é usado em um conjunto, embora PATCH esteja definido para funcionar em mapas / dicionários / objetos com valor-chave.
  • Se alguém realmente deseja aplicar PATCH a conjuntos, existe uma tradução natural que deve ser usada:, t: Set<T> → Map<T, Boolean>definida com x in A iff t(A)(x) == True. Usando essa definição, o patch é deixado como idempotente.
  • No exemplo, essa tradução não foi usada. Em vez disso, o PATCH funciona como um POST. Primeiro de tudo, por que um ID é gerado para o objeto? E quando é gerado? Se o objeto for comparado primeiro aos elementos do conjunto, e se nenhum objeto correspondente for encontrado, o ID será gerado, e novamente o programa deverá funcionar de maneira diferente ( {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:

  • Um exemplo com recursos adicionais gerados seria versionamento. Pode-se manter registro do número de alterações em um único objeto. Nesse caso, PUT não é idempotente: PUT /user/12 {email: "[email protected]"}resulta na {email: "...", version: 1}primeira e {email: "...", version: 2}na segunda vez.
  • Brincando com os IDs, é possível gerar um novo ID toda vez que o objeto é atualizado, resultando em uma PUT não idempotente.

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.

Mohammad-Ali A'RÂBI
fonte
-1

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.

Benjamin
fonte