Panoramica
In questo rapido articolo, vedremo le differenze tra i verbi HTTP PUT e PATCH e la semantica delle due operazioni.
Utilizzeremo Spring per implementare due endpoint REST che supportano questi due tipi di operazioni, e per capire meglio le differenze e il modo giusto di usarli.
Quando usare Put e quando Patch?
Iniziamo con una semplice, e leggermente semplice affermazione.
Quando un client ha bisogno di sostituire interamente una risorsa esistente, può usare PUT. Quando sta facendo un aggiornamento parziale, può usare HTTP PATCH.
Per esempio, quando si aggiorna un singolo campo della Risorsa, inviare la rappresentazione completa della Risorsa potrebbe essere ingombrante e utilizzare molta banda non necessaria. In questi casi, la semantica di PATCH ha molto più senso.
Un altro aspetto importante da considerare qui è l’idempotenza; PUT è idempotente; PATCH può esserlo, ma non è richiesto. E, così – a seconda della semantica dell’operazione che stiamo implementando, possiamo anche scegliere uno o l’altro in base a questa caratteristica.
Implementazione della logica PUT e PATCH
Diciamo che vogliamo implementare l’API REST per aggiornare una HeavyResource con più campi:
public class HeavyResource { private Integer id; private String name; private String address; // ...
Prima di tutto, dobbiamo creare l’endpoint che gestisce un aggiornamento completo della risorsa usando PUT:
@PutMapping("/heavyresource/{id}")public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved");}
Questo è un endpoint standard per aggiornare le risorse.
Ora, diciamo che il campo dell’indirizzo sarà spesso aggiornato dal client. In questo caso, non vogliamo inviare l’intero oggetto HeavyResource con tutti i campi, ma vogliamo la possibilità di aggiornare solo il campo dell’indirizzo – tramite il metodo PATCH.
Possiamo creare un DTO HeavyResourceAddressOnly per rappresentare un aggiornamento parziale del campo dell’indirizzo:
public class HeavyResourceAddressOnly { private Integer id; private String address; // ...}
Poi, possiamo sfruttare il metodo PATCH per inviare un aggiornamento parziale:
@PatchMapping("/heavyresource/{id}")public ResponseEntity<?> partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated");}
Con questo DTO più granulare, possiamo inviare solo il campo che dobbiamo aggiornare – senza l’overhead di inviare l’intera HeavyResource.
Se abbiamo un gran numero di queste operazioni di aggiornamento parziale, possiamo anche saltare la creazione di un DTO personalizzato per ogni uscita – e usare solo una mappa:
@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");}
Questa soluzione ci darà più flessibilità nell’implementazione delle API; tuttavia, perdiamo anche alcune cose – come la validazione.
Testing PUT e PATCH
Infine, scriviamo dei test per entrambi i metodi HTTP. Per prima cosa, vogliamo testare l’aggiornamento della risorsa completa tramite il metodo 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());
L’esecuzione di un aggiornamento parziale si ottiene utilizzando il metodo PATCH:
mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());
Possiamo anche scrivere un test per un approccio più generico:
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());
Handling Partial Requests With Null Values
Quando stiamo scrivendo un’implementazione per un metodo PATCH, dobbiamo specificare un contratto su come trattare i casi in cui otteniamo null come valore per il campo address nella HeavyResourceAddressOnly.
Supponiamo che il client invii la seguente richiesta:
{ "id" : 1, "address" : null}
Allora possiamo gestire questo come impostare un valore del campo indirizzo a null o semplicemente ignorare tale richiesta trattandola come no-change.
Dovremmo scegliere una strategia per gestire null e attenerci ad essa in ogni implementazione del metodo PATCH.
Conclusione
In questo rapido tutorial, ci siamo concentrati sulla comprensione delle differenze tra i metodi HTTP PATCH e PUT.
Abbiamo implementato un semplice controller Spring REST per aggiornare una risorsa tramite il metodo PUT e un aggiornamento parziale usando PATCH.
L’implementazione di tutti questi esempi e frammenti di codice può essere trovata nel progetto GitHub – questo è un progetto Maven, quindi dovrebbe essere facile da importare ed eseguire.
Inizia con Spring 5 e Spring Boot 2, attraverso il corso Learn Spring :
>> CHECK OUT IL CORSO