HTTP PUT vs HTTP PATCH em uma API REST

Overview

Neste artigo rápido, estamos olhando para as diferenças entre os verbos HTTP PUT e PATCH e para a semântica das duas operações.

Usaremos Spring para implementar dois endpoints REST que suportam estes dois tipos de operações, e para entender melhor as diferenças e a maneira correta de usá-las.

Quando usar Put e When Patch?

Comecemos com uma declaração simples, e ligeiramente simples.

Quando um cliente precisa substituir um recurso existente por completo, 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, enviar a representação completa do Recurso pode ser incômodo e utilizar muita largura de banda desnecessária. Em tais casos, a semântica do PATCH faz muito mais sentido.

Outro aspecto importante a considerar aqui é idempotência; PUT é idempotente; PATCH pode ser, mas não é necessário. E assim – dependendo da semântica da operação que estamos a implementar, também podemos escolher uma ou outra com base nesta característica.

Implementando PUT e PATCH Logic

Digamos que queremos implementar a API REST para atualizar um recurso pesado com múltiplos campos:

public class HeavyResource { private Integer id; private String name; private String address; // ...

Primeiro, precisamos criar o endpoint que lide 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 endpoint padrão para atualizar recursos.

Agora, digamos que o campo de endereço será freqüentemente atualizado pelo cliente. Nesse caso, não queremos enviar todo o objeto HeavyResource com todos os campos, mas queremos a capacidade de atualizar apenas o campo de endereço – através do método PATCH.

Podemos criar um DTO HeavyResourceAddressOnly para representar uma atualização parcial do campo de endereço:

public class HeavyResourceAddressOnly { private Integer id; private String address; // ...}

Próximo, podemos utilizar 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 este DTO mais granular, podemos enviar o campo que precisamos atualizar apenas – sem a sobrecarga de enviar todo o HeavyResource.

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<String, Object> 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, perdemos algumas coisas também – como validação.

Testing PUT e PATCH

Finalmente, vamos escrever testes para ambos os métodos HTTP. Primeiro, queremos testar a atualização do recurso completo através do 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());

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());

Podemos também escrever um teste para uma abordagem mais genérica:

HashMap<String, Object> 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());

O tratamento de solicitações parciais com valores nulos

Quando estamos escrevendo uma implementação para um método PATCH, precisamos especificar um contrato de como tratar os casos quando ficamos nulos como um valor para o campo de endereço no método HeavyResourceAddressOnly.

Ponha que o cliente envie a seguinte requisição:

{ "id" : 1, "address" : null}

Então podemos tratar isto como definindo um valor do campo de endereço como nulo ou simplesmente ignorando tal requisição, tratando-o como se não houvesse alteração.

Devemos escolher uma estratégia para lidar com o método nulo e aderir a ela em cada implementação do método PATCH.

Conclusion

Neste tutorial rápido, focamos em entender as diferenças entre os métodos HTTP PATCH e PUT.

Implementamos um simples controlador Spring REST para atualizar um Recurso via método PUT e uma atualização parcial usando PATCH.

A implementação de todos estes exemplos e trechos de código pode ser encontrada no projeto GitHub – este é um projeto Maven, portanto deve ser fácil de importar e rodar como ele é.

>

Comece com Spring 5 e Spring Boot 2, através do curso Learn Spring :

> > CHECK OUT THE COURSE

Deixe uma resposta

O seu endereço de email não será publicado.