Áttekintés
Ebben a gyors cikkben a HTTP PUT és PATCH igék közötti különbségeket és a két művelet szemantikáját vizsgáljuk.
A Spring segítségével két olyan REST végpontot fogunk implementálni, amelyek támogatják ezt a kétféle műveletet, és jobban megértjük a különbségeket és a helyes használatukat.
Mikor használjuk a Put és mikor a Patch-et?
Kezdjük egy egyszerű, és kissé egyszerű megállapítással.
Ha egy ügyfélnek egy meglévő erőforrást kell teljesen lecserélnie, akkor a PUT-ot használhatja. Ha részleges frissítést végez, használhatja a HTTP PATCH-ot.
Az Resource egyetlen mezőjének frissítésekor például a teljes Resource-reprezentáció elküldése nehézkes lehet, és sok felesleges sávszélességet használ. Ilyen esetekben a PATCH szemantikájának sokkal több értelme van.
Egy másik fontos szempont, amit itt figyelembe kell venni, az idempotencia; a PUT idempotenciális; a PATCH lehet, de nem kötelező. És így – a megvalósítandó művelet szemantikájától függően – e tulajdonság alapján is választhatjuk az egyiket vagy a másikat.
PUT és PATCH logika megvalósítása
Tegyük fel, hogy egy több mezőt tartalmazó HeavyResource frissítéséhez szeretnénk megvalósítani a REST API-t:
public class HeavyResource { private Integer id; private String name; private String address; // ...
Először is létre kell hoznunk azt a végpontot, amely az erőforrás teljes frissítését kezeli a PUT használatával:
@PutMapping("/heavyresource/{id}")public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved");}
Ez egy szabványos végpont az erőforrások frissítéséhez.
Tegyük fel, hogy a cím mezőt gyakran fogja frissíteni az ügyfél. Ebben az esetben nem akarjuk elküldeni a teljes HeavyResource objektumot az összes mezővel együtt, hanem csak a cím mező frissítésének lehetőségét szeretnénk – a PATCH metóduson keresztül.
Elkészíthetünk egy HeavyResourceAddressOnly DTO-t a cím mező részleges frissítésének reprezentálásához:
public class HeavyResourceAddressOnly { private Integer id; private String address; // ...}
Következő lépésként kihasználhatjuk a PATCH metódust a részleges frissítés elküldéséhez:
@PatchMapping("/heavyresource/{id}")public ResponseEntity<?> partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated");}
Ezzel a részletesebb DTO-val csak a frissítendő mezőt tudjuk elküldeni – a teljes HeavyResource elküldésének terhei nélkül.
Ha sok ilyen részleges frissítési műveletünk van, akkor kihagyhatjuk az egyéni DTO létrehozását is minden egyes outhoz – és csak egy térképet használhatunk:
@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");}
Ez a megoldás nagyobb rugalmasságot biztosít számunkra az API megvalósításában; azonban néhány dolgot el is veszítünk – például az érvényesítést.
PUT és PATCH tesztelése
Végül írjunk teszteket mindkét HTTP módszerhez. Először a teljes erőforrás PUT módszerrel történő frissítését akarjuk tesztelni:
mockMvc.perform(put("/heavyresource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResource(1, "Tom", "Jackson", 12, "heaven street"))) ).andExpect(status().isOk());
A részleges frissítés végrehajtását a PATCH módszerrel érjük el:
mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());
Egy általánosabb megközelítésre is írhatunk tesztet:
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
Mikor a PATCH módszer implementációját írjuk, meg kell adnunk egy szerződést, hogy hogyan kezeljük azokat az eseteket, amikor a HeavyResourceAddressOnly címmező értékeként null értéket kapunk.
Tegyük fel, hogy az ügyfél a következő kérést küldi:
{ "id" : 1, "address" : null}
Azt úgy kezelhetjük, hogy a cím mező értékét nullára állítjuk, vagy egyszerűen figyelmen kívül hagyjuk az ilyen kérést, és úgy kezeljük, hogy nem változik.
Válasszunk egy stratégiát a null kezelésére, és ragaszkodjunk ehhez minden PATCH módszer implementációjában.
Következtetés
Ebben a gyors bemutatóban a HTTP PATCH és PUT módszerek közötti különbségek megértésére összpontosítottunk.
Egy egyszerű Spring REST vezérlőt implementáltunk egy Resource frissítésére a PUT metódussal és egy részleges frissítésre a PATCH metódussal.
Az összes példa és kódrészlet megvalósítása megtalálható a GitHub projektben – ez egy Maven projekt, így könnyen importálható és futtatható lesz, ahogy van.
Kezdje meg a Spring 5 és a Spring Boot 2 használatát a Tanulj Spring tanfolyamon keresztül :
>> KATTINTSON KI A TANFOLYAMOT