Přehled
V tomto krátkém článku se podíváme na rozdíly mezi slovesy HTTP PUT a PATCH a na sémantiku obou operací.
Pomocí jara budeme implementovat dva koncové body REST, které tyto dva typy operací podporují, a lépe pochopíme rozdíly a správný způsob jejich použití.
Kdy použít Put a kdy Patch?
Začneme jednoduchým a poněkud prostým tvrzením.
Pokud klient potřebuje zcela nahradit existující zdroj, může použít PUT. Když provádí částečnou aktualizaci, může použít HTTP PATCH.
Příklad při aktualizaci jediného pole prostředku může být odeslání kompletní reprezentace prostředku těžkopádné a zbytečně využívá velkou šířku pásma. V takových případech dává sémantika PATCH mnohem větší smysl.
Dalším důležitým aspektem, který je zde třeba zvážit, je idempotence; PUT je idempotentní; PATCH může být, ale nemusí. A tak – v závislosti na sémantice operace, kterou implementujeme, můžeme také na základě této vlastnosti zvolit jednu nebo druhou.
Implementace logiky PUT a PATCH
Řekněme, že chceme implementovat rozhraní REST API pro aktualizaci zdroje HeavyResource s více poli:
public class HeavyResource { private Integer id; private String name; private String address; // ...
Nejprve musíme vytvořit koncový bod, který zpracovává úplnou aktualizaci zdroje pomocí PUT:
@PutMapping("/heavyresource/{id}")public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved");}
Toto je standardní koncový bod pro aktualizaci zdrojů.
Nyní řekněme, že pole adresy bude klient často aktualizovat. V takovém případě nechceme posílat celý objekt HeavyResource se všemi poli, ale chceme mít možnost aktualizovat pouze pole adresy – prostřednictvím metody PATCH.
Můžeme vytvořit DTO HeavyResourceAddressOnly, který bude představovat částečnou aktualizaci pole adresy:
public class HeavyResourceAddressOnly { private Integer id; private String address; // ...}
Dále můžeme využít metodu PATCH k odeslání částečné aktualizace:
@PatchMapping("/heavyresource/{id}")public ResponseEntity<?> partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated");}
S tímto podrobnějším DTO můžeme odeslat pouze pole, které potřebujeme aktualizovat – bez režie odesílání celého HeavyResource.
Pokud máme velké množství těchto částečných aktualizačních operací, můžeme také vynechat vytváření vlastního DTO pro každý out – a použít pouze mapu:
@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");}
Toto řešení nám poskytne větší flexibilitu při implementaci API; nicméně o pár věcí také přijdeme – například o validaci.
Testování PUT a PATCH
Nakonec napíšeme testy pro obě metody HTTP. Nejprve chceme otestovat aktualizaci celého zdroje pomocí metody 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());
Provedení částečné aktualizace dosáhneme pomocí metody PATCH:
mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());
Můžeme také napsat test pro obecnější přístup:
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());
Obsluha částečných požadavků s nulovými hodnotami
Pokud píšeme implementaci pro metodu PATCH, musíme specifikovat smlouvu, jak zacházet s případy, kdy v poli HeavyResourceAddressOnly dostaneme jako hodnotu null.
Předpokládejme, že klient odešle následující požadavek:
{ "id" : 1, "address" : null}
Pak to můžeme řešit jako nastavení hodnoty pole adresy na null nebo takový požadavek prostě ignorovat tím, že ho budeme považovat za beze změny.
Měli bychom si vybrat jednu strategii pro zpracování null a držet se jí v každé implementaci metody PATCH.
Závěr
V tomto krátkém návodu jsme se zaměřili na pochopení rozdílů mezi metodami HTTP PATCH a PUT.
Implementovali jsme jednoduchý řadič Spring REST pro aktualizaci zdroje pomocí metody PUT a částečnou aktualizaci pomocí metody PATCH.
Implementaci všech těchto příkladů a úryvků kódu najdete v projektu GitHub – jedná se o projekt Maven, takže by měl být snadno importovatelný a spustitelný v této podobě.
Začněte se Spring 5 a Spring Boot 2 prostřednictvím kurzu Naučte se Spring :
>> VYHLEDEJTE SI KURZ
.