Devo usar PATCH ou PUT na minha API REST?

274

Quero projetar meu ponto de extremidade restante com o método apropriado para o cenário a seguir.

Existe um grupo. Cada grupo tem um status. O grupo pode ser ativado ou desativado pelo administrador.

Devo projetar meu ponto final como

PUT /groups/api/v1/groups/{group id}/status/activate

OU

PATCH /groups/api/v1/groups/{group id}

with request body like 
{action:activate|deactivate}
java_geek
fonte
1
Ambos estão bem. Mas dê uma olhada no RFC para o formato JSON PATCH ( tools.ietf.org/html/rfc6902 ). O PATCH espera obter algum tipo de documento diff / patch para a carga útil (e o JSON bruto não é um deles).
Jørn Wildt
1
@ JørnWildt não, PUT seria uma escolha horrível. O que você está colocando aí? PATCH é a única opção sensata. Bem, neste caso, você pode usar o formato PATCH apresentado na pergunta e apenas usar o método PUT; o exemplo PUT está errado.
thecoshman
3
Não há nada de errado em expor uma ou mais propriedades como recursos independentes que um cliente pode obter e modificar com PUT. Mas, sim, o URL deve ser / groups / api / v1 / groups / {id do grupo} / status para o qual você pode COLOCAR "ativo" ou "inativo" ou GET para ler o estado atual.
Jørn Wildt
3
Aqui está uma boa explicação de como o PATCH realmente deve ser usado: williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot
rishat
4
" activate" não é uma construção RESTful adequada. Você provavelmente está tentando atualizar statuspara "ativo" ou "desativado". nesse caso, você pode PATCH para .../statuscom a cadeia "ativa" ou "desativada" no corpo. Ou se você está tentando atualizar um booleano em status.active, pode PATCH para .../status/activecom o booleano no corpo #
Augie Gardner

Respostas:

328

O PATCHmétodo é a escolha correta aqui, pois você está atualizando um recurso existente - o ID do grupo. PUTsó deve ser usado se você estiver substituindo um recurso por inteiro.

Mais informações sobre a modificação parcial de recursos estão disponíveis na RFC 5789 . Especificamente, o PUTmétodo é descrito da seguinte maneira:

Vários aplicativos que estendem o HTTP (Hypertext Transfer Protocol) requerem um recurso para fazer modificações parciais nos recursos. O método HTTP PUT existente permite apenas a substituição completa de um documento. Esta proposta adiciona um novo método HTTP, PATCH, para modificar um recurso HTTP existente.

Luke Peterson
fonte
1
Para ser justo, você pode COLOCAR a string 'ativar' ou 'desativar' no recurso. Como parece haver apenas a única coisa a ser trocada, substituí-la completamente não é um problema tão grande. E permite uma solicitação (insignificante) menor.
thecoshman
35
É importante observar que a RFC 5789 ainda está em fase de proposta e não foi oficialmente aceita e atualmente é sinalizada como 'irrata existe'. Essa 'melhor prática' é altamente debatida e tecnicamente o PATCH ainda não faz parte do padrão HTTP.
usar o seguinte código
4
Apenas meus 2 centavos alguns anos depois: você poderia considerar o status em si como um recurso e, nesse caso, usar PUT contra / status tecnicamente estaria substituindo o recurso de status nesse ponto final.
Jono Stewart
3
Eu ousaria argumentar contra os documentos, mesmo que seja "a" RFC. Os documentos afirmam que você deve usar PATCH para modificar apenas uma parte de um recurso, mas omitiu o importante: o método PATCH é definido como um método não-idempotente. Por quê? Se o método PUT foi criado com a atualização / substituição de todo o recurso em mente, por que o método PATCH não foi criado como um método idempotente como o PUT, se seu objetivo era apenas atualizar a parte de um recurso? Para mim, parece mais uma diferença na idempotência da atualização, como "a = 5" (PUT) e "a = a + 5" (PATCH). Ambos podem atualizar o recurso inteiro.
Mladen B.
179

O R em RESTO significa recurso

(O que não é verdade, porque representa Representational, mas é um bom truque lembrar a importância dos Recursos no REST).

Sobre PUT /groups/api/v1/groups/{group id}/status/activate: você não está atualizando um "ativar". Um "ativar" não é uma coisa, é um verbo. Os verbos nunca são bons recursos. Uma regra prática : se a ação, um verbo, estiver na URL, provavelmente não é RESTful .

O que você está fazendo? Você está "adicionando", "removendo" ou "atualizando" uma ativação em um grupo ou, se preferir: manipulando um recurso "status" em um grupo. Pessoalmente, eu usaria "ativações" porque elas são menos ambíguas que o conceito "status": criar um status é ambíguo, criar uma ativação não é.

  • POST /groups/{group id}/activation Cria (ou solicita a criação de) uma ativação.
  • PATCH /groups/{group id}/activationAtualiza alguns detalhes de uma ativação existente. Como um grupo tem apenas uma ativação, sabemos a que recurso de ativação estamos nos referindo.
  • PUT /groups/{group id}/activationInsere ou substitui a ativação antiga. Como um grupo tem apenas uma ativação, sabemos a que recurso de ativação estamos nos referindo.
  • DELETE /groups/{group id}/activation Irá cancelar ou remover a ativação.

Esse padrão é útil quando a "ativação" de um Grupo tem efeitos colaterais, como pagamentos sendo feitos, e-mails sendo enviados etc. Somente POST e PATCH podem ter esses efeitos colaterais. Quando, por exemplo, a exclusão de uma ativação precisa, por exemplo, notificar os usuários por email, DELETE não é a escolha certa; nesse caso, você provavelmente vai querer criar um recurso de desativação : POST /groups/{group_id}/deactivation.

É uma boa idéia seguir essas diretrizes, porque este contrato padrão deixa muito claro para seus clientes e todos os proxies e camadas entre o cliente e você, sabe quando é seguro tentar novamente e quando não. Digamos que o cliente esteja em algum lugar com wifi esquisito e seu usuário clique em "desativar", o que desencadeia um DELETE: Se isso falhar, o cliente pode simplesmente tentar novamente, até obter 404, 200 ou qualquer outra coisa que possa manipular. Mas se dispara um, POST to deactivationele sabe que não deve tentar novamente: o POST implica isso.
Agora, qualquer cliente tem um contrato que, quando seguido, protege contra o envio de 42 e-mails "seu grupo foi desativado", simplesmente porque sua biblioteca HTTP continuava repetindo a chamada para o back-end.

Atualizando um Único Atributo: Use PATCH

PATCH /groups/{group id}

Caso você deseje atualizar um atributo. Por exemplo, o "status" pode ser um atributo nos Grupos que podem ser definidos. Um atributo como "status" geralmente é um bom candidato para limitar a uma lista de permissões de valores. Os exemplos usam algum esquema JSON indefinido:

PATCH /groups/{group id} { "attributes": { "status": "active" } }
response: 200 OK

PATCH /groups/{group id} { "attributes": { "status": "deleted" } }
response: 406 Not Acceptable

Substituindo o recurso, sem efeitos colaterais, use PUT.

PUT /groups/{group id}

Caso você deseje substituir um grupo inteiro. Isso não significa necessariamente que o servidor realmente crie um novo grupo e jogue fora o antigo, por exemplo, os IDs podem permanecer os mesmos. Mas para os clientes, é isso que PUT pode significar: o cliente deve assumir que ele recebe um item totalmente novo, com base na resposta do servidor.

O cliente deve, no caso de uma PUTsolicitação, sempre enviar o recurso inteiro, com todos os dados necessários para criar um novo item: geralmente os mesmos dados que uma criação do POST exigiria.

PUT /groups/{group id} { "attributes": { "status": "active" } }
response: 406 Not Acceptable

PUT /groups/{group id} { "attributes": { "name": .... etc. "status": "active" } }
response: 201 Created or 200 OK, depending on whether we made a new one.

Um requisito muito importante é que PUTseja idempotente: se você precisar de efeitos colaterais ao atualizar um grupo (ou alterar uma ativação), deverá usá-lo PATCH. Portanto, quando a atualização resultar, por exemplo, no envio de um email, não use PUT.

berkes
fonte
3
Isso foi muito informativo para mim. "Este padrão é útil quando a 'ativação' de um grupo tem efeitos colaterais" - Como é que este padrão é útil, especificamente no que diz respeito a quando as ações têm efeitos colaterais, ao contrário dos endpoints iniciais OP
Abdul
1
@ Abdul, o padrão é útil por várias razões, mas, por causa dos efeitos colaterais, deve ficar muito claro para o cliente quais são os efeitos de uma ação. Quando, digamos, um aplicativo iOS decide enviar o catálogo de endereços inteiro como "contatos", deve ficar extremamente claro quais efeitos colaterais a criação, atualização, exclusão etc. de um Contato tem. Para evitar o envio em massa de todos os contatos, por exemplo.
berkes
1
No RESTfull, o PUT também pode alterar as entidades Identity - por exemplo, o PrimaryKey ID, onde pode causar uma falha na solicitação paralela. (por exemplo, a atualização de toda a entidade precisa excluir algumas linhas e adicionar novas, criando assim novas entidades). Onde PATCH nunca deve ser capaz de fazer isso, permitindo um número ilimitado de solicitações PATCH sem afetar outros "aplicativos"
Piotr Kula
1
Resposta muito útil. Obrigado! Eu também adicionaria um comentário, assim como na resposta de Luke, apontando que a diferença entre PUT / PATCH não é apenas a atualização total / parcial, mas também a idempotência que é diferente. Não foi um erro, foi uma decisão intencional e acho que muitas pessoas não levam isso em consideração ao decidir o uso do método HTTP.
Mladen B.
1
Os serviços @richremer, como os modelos, são abstrações internas. Assim como é uma abstração ruim exigir uma relação de 1 a 1 entre os modelos de pontos de extremidade REST e ORM ou mesmo tabelas de banco de dados, é uma abstração ruim expor os Serviços. A parte externa, sua API, deve comunicar modelos de domínio. Como você os implementa internamente não interessa à API. Você deve estar livre para mudar de um ActivationService para um fluxo de ativação baseado em CQRS, sem precisar alterar sua API.
Berkes
12

Eu recomendaria usar PATCH, porque o recurso 'group' possui muitas propriedades, mas, neste caso, você está atualizando apenas o campo de ativação (modificação parcial)

de acordo com o RFC5789 ( https://tools.ietf.org/html/rfc5789 )

O método HTTP PUT existente permite apenas a substituição completa de um documento. Esta proposta adiciona um novo método HTTP, PATCH, para modificar um recurso HTTP existente.

Além disso, em mais detalhes,

A diferença entre as solicitações PUT e PATCH é refletida na maneira como o servidor processa a entidade fechada para modificar o recurso
identificado pelo Request-URI. Em uma solicitação PUT, a entidade fechada é 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 não é seguro nem idempotente, conforme definido por [RFC2616], Seção 9.1.

Os clientes precisam escolher quando usar PATCH em vez de PUT. Por
exemplo, se o tamanho do documento de correção for maior que o tamanho dos
novos dados de recurso que seriam usados ​​em uma PUT, poderá fazer
sentido usar PUT em vez de PATCH. Uma comparação com o POST é ainda mais difícil, porque o POST é usado de maneiras muito variadas e pode
incluir operações do tipo PUT e PATCH, se o servidor escolher. Se
a operação não modificar o recurso identificado pelo RequestURI de maneira previsível, o POST deverá ser considerado em vez de PATCH
ou PUT.

O código de resposta para PATCH é

O código de resposta 204 é usado porque a resposta não carrega um corpo da mensagem (que teria uma resposta com o código 200). Observe que outros códigos de sucesso também podem ser usados.

consulte também thttp: //restcookbook.com/HTTP%20Methods/patch/

Advertência: Uma API que implementa PATCH deve ser corrigida atomicamente. DEVE não ser possível que os recursos sejam corrigidos pela metade quando solicitados por um GET.

Clojurevangelist
fonte
7

Como você deseja projetar uma API usando o estilo de arquitetura REST, é necessário pensar em seus casos de uso para decidir quais conceitos são importantes o suficiente para expor como recursos. Caso decida expor o status de um grupo como um sub-recurso, você pode fornecer o seguinte URI e implementar o suporte aos métodos GET e PUT:

/groups/api/groups/{group id}/status

A desvantagem dessa abordagem sobre o PATCH para modificação é que você não poderá fazer alterações em mais de uma propriedade de um grupo de maneira atomica e transacional. Se alterações transacionais forem importantes, use PATCH.

Se você decidir expor o status como um sub-recurso de um grupo, deve ser um link na representação do grupo. Por exemplo, se o agente obtiver o grupo 123 e aceitar XML, o corpo da resposta poderá conter:

<group id="123">
  <status>Active</status>
  <link rel="/linkrels/groups/status" uri="/groups/api/groups/123/status"/>
  ...
</group>

É necessário um hiperlink para atender a hipermídia como o mecanismo da condição de estado do aplicativo do estilo de arquitetura REST.

Andrew Dobrowolski
fonte
0

Eu geralmente preferiria algo um pouco mais simples, como activate/ deactivatesub-recurso (vinculado por um Linkcabeçalho com rel=service).

POST /groups/api/v1/groups/{group id}/activate

ou

POST /groups/api/v1/groups/{group id}/deactivate

Para o consumidor, essa interface é simples e segue os princípios REST sem atrapalhar a concepção de "ativações" como recursos individuais.

remer rico
fonte
0

Uma opção possível para implementar esse comportamento é

PUT /groups/api/v1/groups/{group id}/status
{
    "Status":"Activated"
}

E, obviamente, se alguém precisar desativá-lo, PUTteráDeactivated status no JSON.

Em caso de necessidade de ativação / desativação em massa, PATCHpode entrar no jogo (não para o grupo exato, mas para o groupsrecurso:

PATCH /groups/api/v1/groups
{
    { “op”: “replace”, “path”: “/group1/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group7/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group9/status”, “value”: “Deactivated” }
}

Em geral, essa é uma idéia sugerida por @Andrew Dobrowolski, mas com pequenas alterações na realização exata.

Ivan Sokalskiy
fonte