Overview
În acest articol rapid, analizăm diferențele dintre verbele HTTP PUT și PATCH și semantica celor două operații.
Vom folosi Spring pentru a implementa două puncte finale REST care suportă aceste două tipuri de operații și pentru a înțelege mai bine diferențele și modul corect de a le folosi.
Când să folosim Put și când Patch?
Să începem cu o afirmație simplă și ușor simplă.
Când un client trebuie să înlocuiască în întregime o resursă existentă, poate folosi PUT. Atunci când fac o actualizare parțială, pot folosi HTTP PATCH.
De exemplu, atunci când actualizează un singur câmp al resursei, trimiterea reprezentării complete a resursei ar putea fi greoaie și utilizează multă lățime de bandă inutilă. În astfel de cazuri, semantica lui PATCH are mult mai mult sens.
Un alt aspect important care trebuie luat în considerare aici este idempotența; PUT este idempotent; PATCH poate fi, dar nu este necesar să fie. Și, astfel – în funcție de semantica operației pe care o implementăm, putem alege una sau alta și pe baza acestei caracteristici.
Implementarea logicii PUT și PATCH
Să presupunem că dorim să implementăm API-ul REST pentru actualizarea unei resurse HeavyResource cu mai multe câmpuri:
public class HeavyResource { private Integer id; private String name; private String address; // ...
În primul rând, trebuie să creăm punctul final care gestionează o actualizare completă a resursei folosind PUT:
@PutMapping("/heavyresource/{id}")public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved");}
Acesta este un punct final standard pentru actualizarea resurselor.
Acum, să spunem că acel câmp de adresă va fi adesea actualizat de către client. În acest caz, nu dorim să trimitem întregul obiect HeavyResource cu toate câmpurile, dar dorim să avem posibilitatea de a actualiza doar câmpul adresă – prin metoda PATCH.
Potem crea un DTO HeavyResourceAddressOnly pentru a reprezenta o actualizare parțială a câmpului de adresă:
public class HeavyResourceAddressOnly { private Integer id; private String address; // ...}
În continuare, putem folosi metoda PATCH pentru a trimite o actualizare parțială:
@PatchMapping("/heavyresource/{id}")public ResponseEntity<?> partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated");}
Cu acest DTO mai granular, putem trimite doar câmpul pe care trebuie să îl actualizăm – fără a mai fi nevoie să trimitem întregul HeavyResource.
Dacă avem un număr mare de astfel de operații de actualizare parțială, putem, de asemenea, să sărim peste crearea unui DTO personalizat pentru fiecare ieșire – și să folosim doar o hartă:
@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");}
Această soluție ne va oferi mai multă flexibilitate în implementarea API-ului; cu toate acestea, pierdem, de asemenea, câteva lucruri – cum ar fi validarea.
Testarea PUT și PATCH
În cele din urmă, să scriem teste pentru ambele metode HTTP. În primul rând, dorim să testăm actualizarea resursei complete prin metoda 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());
Executarea unei actualizări parțiale se realizează prin utilizarea metodei PATCH:
mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());
De asemenea, putem scrie un test pentru o abordare mai generică:
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());
Manipularea cererilor parțiale cu valori nule
Când scriem o implementare pentru o metodă PATCH, trebuie să specificăm un contract privind modul de tratare a cazurilor în care primim null ca valoare pentru câmpul address din HeavyResourceAddressOnly.
Să presupunem că clientul trimite următoarea cerere:
{ "id" : 1, "address" : null}
Atunci putem trata acest lucru ca și cum am seta o valoare a câmpului de adresă ca fiind nulă sau pur și simplu să ignorăm o astfel de cerere, tratând-o ca fiind fără modificări.
Ar trebui să alegem o strategie de tratare a lui null și să rămânem la ea în fiecare implementare a metodei PATCH.
Concluzie
În acest tutorial rapid, ne-am concentrat pe înțelegerea diferențelor dintre metodele HTTP PATCH și PUT.
Am implementat un simplu controler Spring REST pentru a actualiza o resursă prin metoda PUT și o actualizare parțială folosind PATCH.
Implementarea tuturor acestor exemple și fragmente de cod poate fi găsită în proiectul GitHub – acesta este un proiect Maven, deci ar trebui să fie ușor de importat și de rulat așa cum este.
Începeți să vă familiarizați cu Spring 5 și Spring Boot 2, prin intermediul cursului Learn Spring :
>>VEZI CURSUL
.