Översikt
I den här artikeln tittar vi på skillnaderna mellan verben HTTP PUT och PATCH och på semantiken för de två operationerna.
Vi kommer att använda Spring för att implementera två REST-slutpunkter som stöder dessa två typer av operationer, och för att bättre förstå skillnaderna och rätt sätt att använda dem.
När man ska använda Put och när man ska patcha?
Låt oss börja med ett enkelt och något enkelt uttalande.
När en klient behöver byta ut en befintlig resurs helt och hållet, kan de använda PUT. När de gör en partiell uppdatering kan de använda HTTP PATCH.
Till exempel när de uppdaterar ett enda fält i resursen kan det vara besvärligt att skicka hela resursrepresentationen och använda mycket onödig bandbredd. I sådana fall är PATCHs semantik mycket mer meningsfull.
En annan viktig aspekt att beakta här är idempotens; PUT är idempotent; PATCH kan vara det, men behöver inte vara det. Beroende på semantiken för den operation vi implementerar kan vi också välja det ena eller det andra baserat på denna egenskap.
Implementering av PUT- och PATCH-logik
Vad sägs om att vi vill implementera REST API:et för att uppdatera en HeavyResource med flera fält:
public class HeavyResource { private Integer id; private String name; private String address; // ...
För det första måste vi skapa den slutpunkt som hanterar en fullständig uppdatering av resursen med hjälp av PUT:
@PutMapping("/heavyresource/{id}")public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved");}
Det här är en standard slutpunkt för uppdatering av resurser.
Nu säger vi att adressfältet ofta kommer att uppdateras av klienten. I det fallet vill vi inte skicka hela HeavyResource-objektet med alla fält, men vi vill ha möjlighet att bara uppdatera adressfältet – via PATCH-metoden.
Vi kan skapa en HeavyResourceAddressOnly DTO för att representera en partiell uppdatering av adressfältet:
public class HeavyResourceAddressOnly { private Integer id; private String address; // ...}
Nästan kan vi utnyttja PATCH-metoden för att skicka en partiell uppdatering:
@PatchMapping("/heavyresource/{id}")public ResponseEntity<?> partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated");}
Med den här mer detaljerade DTO:n kan vi skicka endast det fält som vi behöver uppdatera – utan att behöva skicka hela HeavyResource.
Om vi har ett stort antal av dessa partiella uppdateringsoperationer kan vi också hoppa över skapandet av en anpassad DTO för varje ut – och bara använda en karta:
@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");}
Den här lösningen ger oss mer flexibilitet när vi implementerar API:et, men vi förlorar också några saker – till exempel validering.
Testning av PUT och PATCH
Slutligt, låt oss skriva tester för båda HTTP-metoderna. Först vill vi testa uppdateringen av hela resursen via PUT-metoden:
mockMvc.perform(put("/heavyresource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResource(1, "Tom", "Jackson", 12, "heaven street"))) ).andExpect(status().isOk());
Exekvering av en partiell uppdatering sker med hjälp av PATCH-metoden:
mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());
Vi kan också skriva ett test för ett mer generiskt tillvägagångssätt:
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());
Hantering av delförfrågningar med nollvärden
När vi skriver en implementering för en PATCH-metod måste vi specificera ett kontrakt för hur vi ska behandla fall där vi får null som värde för adressfältet i HeavyResourceAddressOnly.
Antag att klienten skickar följande begäran:
{ "id" : 1, "address" : null}
Då kan vi hantera detta som att ställa in ett värde för adressfältet till null eller bara ignorera en sådan begäran genom att behandla den som no-change.
Vi bör välja en strategi för att hantera null och hålla oss till den i varje implementering av PATCH-metoden.
Slutsats
I den här snabba handledningen fokuserade vi på att förstå skillnaderna mellan HTTP PATCH- och PUT-metoderna.
Vi implementerade en enkel Spring REST-kontroller för att uppdatera en Resource via PUT-metoden och en partiell uppdatering med hjälp av PATCH.
Implementationen av alla dessa exempel och kodutdrag finns i GitHub-projektet – detta är ett Maven-projekt, så det borde vara enkelt att importera och köra som det är.
Kom igång med Spring 5 och Spring Boot 2 genom kursen Lär dig Spring :
>> CHECK OUT THE COURSE