Yleiskatsaus
Tässä pika-artikkelissa tarkastelemme HTTP PUT- ja PATCH-verbien välisiä eroja ja näiden kahden operaation semantiikkaa.
Toteutamme Springin avulla kaksi REST-päätepistettä, jotka tukevat näitä kahta operaatiotyyppiä, ja ymmärrämme paremmin niiden eroja ja oikeaa tapaa käyttää niitä.
Milloin käytetään Put- ja milloin Patch-verbiä?
Aloitetaan yksinkertaisella ja hieman yksinkertaisella toteamuksella.
Kun asiakkaan on korvattava olemassaoleva resurssi kokonaan, hän voi käyttää PUT-verbiä. Kun se tekee osittaisen päivityksen, se voi käyttää HTTP PATCH:ia.
Esimerkiksi resurssin yhtä kenttää päivitettäessä koko resurssin esityksen lähettäminen saattaa olla hankalaa ja käyttää paljon tarpeetonta kaistanleveyttä. Tällaisissa tapauksissa PATCHin semantiikka on paljon järkevämpää.
Toinen tärkeä näkökohta on idempotenssi; PUT on idempotentti; PATCH voi olla, mutta sen ei tarvitse olla. Ja niinpä – riippuen toteuttamamme operaation semantiikasta, voimme myös valita jommankumman tämän ominaisuuden perusteella.
PUT- ja PATCH-logiikan toteuttaminen
Asettakaamme, että haluamme toteuttaa REST-API:n HeavyResource-resurssin, jossa on useita kenttiä, päivittämistä varten:
public class HeavyResource { private Integer id; private String name; private String address; // ...
Esimerkiksi meidän on luotava päätepiste, joka käsittelee resurssin täydellisen päivittämisen PUT:ia käyttäen:
@PutMapping("/heavyresource/{id}")public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved");}
Tämä on resurssien päivittämisen vakiopäätepiste.
Nyt sanotaan, että osoitekenttä päivitetään usein asiakkaan toimesta. Tällöin emme halua lähettää koko HeavyResource-objektia kaikkine kenttineen, vaan haluamme mahdollisuuden päivittää vain osoitekentän – PATCH-metodin kautta.
Voidaan luoda HeavyResourceAddressOnly DTO, joka edustaa osoitekentän osittaista päivitystä:
public class HeavyResourceAddressOnly { private Integer id; private String address; // ...}
Muuten voimme hyödyntää PATCH-metodia osittaisen päivityksen lähettämiseen:
@PatchMapping("/heavyresource/{id}")public ResponseEntity<?> partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated");}
Tämän tarkemman DTO:n avulla voimme lähettää vain päivitettävän kentän – ilman koko HeavyResourcen lähettämisen yleiskustannuksia.
Jos meillä on suuri määrä näitä osittaisia päivitysoperaatioita, voimme myös ohittaa mukautetun DTO:n luomisen jokaiselle ulos – ja käyttää vain karttaa:
@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");}
Tämä ratkaisu antaa meille enemmän joustavuutta API:n toteuttamisessa; kuitenkin menetämme myös muutamia asioita – kuten validoinnin.
PUT:n ja PATCH:n testaaminen
Kirjoitetaan lopuksi testit molemmille HTTP-menetelmille. Ensin haluamme testata koko resurssin päivittämistä PUT-menetelmällä:
mockMvc.perform(put("/heavyresource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResource(1, "Tom", "Jackson", 12, "heaven street"))) ).andExpect(status().isOk());
Osittaisen päivityksen suorittaminen onnistuu PATCH-menetelmällä:
mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());
Voimmekin kirjoittaa testin yleisempää lähestymistapaa varten:
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
Kun kirjoitamme toteutusta PATCH-metodille, meidän on määriteltävä sopimus siitä, miten käsittelemme tapauksia, joissa saamme osoitekentän arvoksi nollan HeavyResourceAddressOnly.
Es oletetaan, että asiakas lähettää seuraavan pyynnön:
{ "id" : 1, "address" : null}
Tällöin voimme käsitellä tätä asettamalla osoitekentän arvoksi nolla tai vain jättää tällaisen pyynnön huomiotta käsittelemällä sitä ei-muutoksena.
Meidän tulisi valita yksi strategia nollan käsittelyyn ja pitäytyä siinä jokaisessa PATCH-metodin toteutuksessa.
Johtopäätös
Tässä pikaoppaassa keskityimme ymmärtämään HTTP PATCH- ja PUT-metodien välisiä eroja.
Toteutimme yksinkertaisen Spring REST -kontrollerin resurssin päivittämiseen PUT-metodilla ja osittaiseen päivitykseen PATCH-metodilla.
Kaikkien näiden esimerkkien ja koodinpätkien toteutukset löytyvät GitHub-projektista – tämä on Maven-projekti, joten sen pitäisi olla helppo tuoda ja ajaa sellaisenaan.
Aloita Spring 5:n ja Spring Boot 2:n käyttö Opi Spring -kurssilla :
>> KATSO KURSSI