HTTP PUT vs HTTP PATCH em uma API REST
1. Visão geral
Neste artigo rápido, estamos examinando as diferenças entre os verbos HTTP PUT e PATCH e na semântica das duas operações.
Usaremos Spring para implementar dois endpoints REST que suportam esses dois tipos de operações e para entender melhor as diferenças e a maneira correta de usá-los.
2. Quando usar Put e quando patch?
Vamos começar com uma declaração simples e ligeiramente simples.
Quando um cliente precisa substituir um Recurso existente inteiramente, ele pode usar PUT. Quando eles estão fazendo uma atualização parcial, eles podem usar HTTP PATCH.
Por exemplo, ao atualizar um único campo do Recurso, o envio da representação completa do Recurso pode ser complicado e utiliza muita largura de banda desnecessária. Nesses casos, a semântica do PATCH faz muito mais sentido.
Outro aspecto importante a considerar aqui éidempotence; PUT is idempotent; PATCH can be, but isn’t required to. E, portanto - dependendo da semântica da operação que estamos implementando, também podemos escolher um ou outro com base nessa característica.
3. Implementando Lógica PUT e PATCH
Digamos que queremos implementar a API REST para atualizar umHeavyResource com vários campos:
public class HeavyResource {
private Integer id;
private String name;
private String address;
// ...
Primeiro, precisamos criar o terminal que lida com uma atualização completa do recurso usando PUT:
@PutMapping("/heavyresource/{id}")
public ResponseEntity> saveResource(@RequestBody HeavyResource heavyResource,
@PathVariable("id") String id) {
heavyResourceRepository.save(heavyResource, id);
return ResponseEntity.ok("resource saved");
}
Este é um terminal padrão para atualizar recursos.
Agora, digamos que o campo de endereço seja frequentemente atualizado pelo cliente. Nesse caso,we don’t want to send the whole HeavyResource object with all fields, mas queremos a capacidade de atualizar apenas o campoaddress - por meio do método PATCH.
Podemos criar um DTOHeavyResourceAddressOnly para representar uma atualização parcial do campo de endereço:
public class HeavyResourceAddressOnly {
private Integer id;
private String address;
// ...
}
Em seguida, podemos aproveitar o método PATCH para enviar uma atualização parcial:
@PatchMapping("/heavyresource/{id}")
public ResponseEntity> partialUpdateName(
@RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) {
heavyResourceRepository.save(partialUpdate, id);
return ResponseEntity.ok("resource address updated");
}
Com esse DTO mais granular, podemos enviar apenas o campo que precisamos atualizar - sem a sobrecarga de enviarHeavyResource inteiro.
Se tivermos um grande número dessas operações de atualização parcial, também podemos pular a criação de um DTO personalizado para cada saída - e usar apenas um mapa:
@RequestMapping(value = "/heavyresource/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity> partialUpdateGeneric(
@RequestBody Map updates,
@PathVariable("id") String id) {
heavyResourceRepository.save(updates, id);
return ResponseEntity.ok("resource updated");
}
Essa solução nos dará mais flexibilidade na implementação da API; no entanto, também perdemos algumas coisas - como a validação.
4. Testando PUT e PATCH
Finalmente, vamos escrever testes para ambos os métodos HTTP. Primeiro, queremos testar a atualização do recurso completo via método PUT:
mockMvc.perform(put("/heavyresource/1")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(
new HeavyResource(1, "Tom", "Jackson", 12, "heaven street")))
).andExpect(status().isOk());
A execução de uma atualização parcial é obtida usando o método PATCH:
mockMvc.perform(patch("/heavyrecource/1")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(
new HeavyResourceAddressOnly(1, "5th avenue")))
).andExpect(status().isOk());
Também podemos escrever um teste para uma abordagem mais genérica:
HashMap updates = new HashMap<>();
updates.put("address", "5th avenue");
mockMvc.perform(patch("/heavyresource/1")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(updates))
).andExpect(status().isOk());
5. Tratamento de solicitações parciais com valoresNull
Quando estamos escrevendo uma implementação para um método PATCH, precisamos especificar um contrato de como tratar casos quando obtemosnull como um valor para o campoaddress emHeavyResourceAddressOnly.
Suponha que o cliente envie a seguinte solicitação:
{
"id" : 1,
"address" : null
}
Então, podemos lidar com isso definindo um valor do campoaddress paranull ou simplesmente ignorando essa solicitação tratando-a como sem alteração.
Devemos escolher uma estratégia para lidar comnulle segui-la em cada implementação do método PATCH.
6. Conclusão
Neste tutorial rápido, focamos em entender as diferenças entre os métodos HTTP PATCH e PUT.
Implementamos um controlador Spring REST simples para atualizar um recurso via método PUT e uma atualização parcial usando PATCH.
A implementação de todos esses exemplos e trechos de código pode ser encontrada emGitHub project - este é um projeto Maven, portanto, deve ser fácil de importar e executar como está.