HTTP PUT vs. HTTP PATCH REST API:ssa

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

Vastaa

Sähköpostiosoitettasi ei julkaista.