HTTP PUT vs HTTP PATCH en una API REST

Resumen

En este artículo rápido, vamos a ver las diferencias entre los verbos HTTP PUT y PATCH y la semántica de las dos operaciones.

Utilizaremos Spring para implementar dos endpoints REST que soportan estos dos tipos de operaciones, y para entender mejor las diferencias y la forma correcta de utilizarlos.

¿Cuándo usar Put y cuándo Patch?

Comencemos con una afirmación simple y ligeramente sencilla.

Cuando un cliente necesita reemplazar un Recurso existente por completo, puede utilizar PUT. Cuando están haciendo una actualización parcial, pueden utilizar HTTP PATCH.

Por ejemplo, cuando se actualiza un solo campo del Recurso, el envío de la representación completa del Recurso puede ser engorroso y utiliza una gran cantidad de ancho de banda innecesario. En estos 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 necesario. Y, por tanto, dependiendo de la semántica de la operación que estemos implementando, también podemos elegir una u otra en función de esta característica.

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, tenemos que crear el endpoint que gestiona una actualización completa del recurso utilizando 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 endpoint estándar para actualizar recursos.

Ahora, digamos que el campo de la dirección será actualizado a menudo por el cliente. En ese caso, no queremos enviar todo el objeto HeavyResource con todos los campos, sino que queremos la capacidad de actualizar sólo el campo de la dirección – a través del método PATCH.

Podemos crear un DTO HeavyResourceAddressOnly para representar una actualización parcial del campo 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 sólo el campo que necesitamos actualizar – sin la sobrecarga de enviar todo el HeavyResource.

Si tenemos un gran número de estas operaciones de actualización parcial, también podemos omitir la creación de un DTO personalizado para cada salida – y sólo utilizar un 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");}

Esta solución nos dará más flexibilidad en la implementación de la API; sin embargo, también perdemos algunas cosas – como la validación.

Probando PUT y PATCH

Por último, vamos a escribir pruebas para ambos métodos HTTP. En primer lugar, queremos probar la actualización del recurso completo mediante el 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 consigue 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<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());

Tratamiento 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 en los que obtenemos null como valor para el campo dirección en el HeavyResourceAddressOnly.

Supongamos que el cliente envía la siguiente petición:

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

Entonces podemos manejar esto como establecer un valor del campo dirección a null o simplemente ignorar dicha petición tratándola como no-cambio.

Deberíamos elegir una estrategia para el manejo de null y ceñirnos a ella en cada implementación del método PATCH.

Conclusión

En este rápido tutorial, nos hemos centrado en entender las diferencias entre los métodos HTTP PATCH y PUT.

Hemos implementado un sencillo controlador Spring REST para actualizar un Recurso a través del método PUT y una actualización parcial utilizando PATCH.

La implementación de todos estos ejemplos y fragmentos de código se pueden encontrar en el proyecto de GitHub – se trata de un proyecto Maven, por lo que debería ser fácil de importar y ejecutar tal cual.

Inicie con Spring 5 y Spring Boot 2, a través del curso Learn Spring : >> CONSULTE EL CURSO

Deja una respuesta

Tu dirección de correo electrónico no será publicada.