1. Información general
En este artículo rápido, examinamos las diferencias entre los verbos HTTP PUT y PATCH y la semántica de las dos operaciones.
Usaremos Spring para implementar dos puntos finales REST que admitan estos dos tipos de operaciones y para comprender mejor las diferencias y la forma correcta de usarlas.
2. ¿Cuándo usar Put y Cuándo Patch?
Comencemos con una declaración simple y un poco simple.
Cuando un cliente necesita reemplazar un recurso existente por completo, puede usar PUT. Cuando están haciendo una actualización parcial, pueden usar HTTP PATCH.
Por ejemplo, al actualizar un solo campo del Recurso, enviar la representación completa del Recurso puede resultar engorroso y utilizar una gran cantidad de ancho de banda innecesario. En tales casos, la semántica de PATCH tiene mucho más sentido.
Otro aspecto importante a considerar aquí es la idempotencia; PUT es idempotente; PATCH puede serlo, pero no es obligatorio . Y, entonces, dependiendo de la semántica de la operación que estamos implementando, también podemos elegir una u otra en función de esta característica.
3. Implementación de la lógica PUT y PATCH
Digamos que queremos implementar la API REST para actualizar un HeavyResource con múltiples campos:
public class HeavyResource { private Integer id; private String name; private String address; // ...
Primero, necesitamos crear el punto final que maneja una actualización completa del 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 es un punto final estándar para actualizar recursos.
Ahora, supongamos que el cliente suele actualizar el campo de dirección. En ese caso, no queremos enviar el objeto HeavyResource completo con todos los campos , pero queremos la capacidad de actualizar solo el campo de dirección , a través del método PATCH.
Podemos crear un DTO HeavyResourceAddressOnly para representar una actualización parcial del campo de dirección:
public class HeavyResourceAddressOnly { private Integer id; private String address; // ... }
A continuación, podemos aprovechar el método PATCH para enviar una actualización parcial:
@PatchMapping("/heavyresource/{id}") public ResponseEntity partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated"); }
Con este DTO más granular, podemos enviar el campo que necesitamos actualizar solo, sin la sobrecarga de enviar HeavyResource completo .
Si tenemos una gran cantidad de estas operaciones de actualización parcial, también podemos omitir la creación de un DTO personalizado para cada salida, y solo usar un 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"); }
Esta solución nos dará más flexibilidad para implementar API; sin embargo, también perdemos algunas cosas, como la validación.
4. Prueba de PUT y PATCH
Finalmente, escribamos pruebas para ambos métodos HTTP. Primero, queremos probar la actualización del recurso completo a través del 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());
La ejecución de una actualización parcial se logra mediante el método PATCH:
mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());
También podemos escribir una prueba para un enfoque más genérico:
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. Manejo de solicitudes parciales con valores nulos
Cuando escribimos una implementación para un método PATCH, necesitamos especificar un contrato de cómo tratar los casos cuando obtenemos un valor nulo para el campo de dirección en HeavyResourceAddressOnly.
Supongamos que el cliente envía la siguiente solicitud:
{ "id" : 1, "address" : null }
Entonces podemos manejar esto estableciendo un valor del campo de dirección en nulo o simplemente ignorando dicha solicitud tratándola como sin cambios.
Deberíamos elegir una estrategia para manejar nulos y ceñirnos a ella en cada implementación del método PATCH.
6. Conclusión
En este tutorial rápido, nos enfocamos en comprender las diferencias entre los métodos HTTP PATCH y PUT.
Implementamos un controlador Spring REST simple para actualizar un recurso a través del método PUT y una actualización parcial usando PATCH.
La implementación de todos estos ejemplos y fragmentos de código se puede encontrar en el proyecto GitHub; este es un proyecto de Maven, por lo que debería ser fácil de importar y ejecutar tal como está.