HTTP PUT vs HTTP PATCH w REST API

Przegląd

W tym krótkim artykule, przyjrzymy się różnicom pomiędzy czasownikami HTTP PUT i PATCH oraz semantyce tych dwóch operacji.

Użyjemy Springa aby zaimplementować dwa punkty końcowe REST, które obsługują te dwa typy operacji, oraz aby lepiej zrozumieć różnice i właściwy sposób ich użycia.

Kiedy używać Put a kiedy Patch?

Zacznijmy od prostego i nieco prostego stwierdzenia.

Gdy klient potrzebuje całkowicie zastąpić istniejący zasób, może użyć PUT. Gdy dokonuje częściowej aktualizacji, może użyć HTTP PATCH.

Na przykład, gdy aktualizuje pojedyncze pole Zasobu, wysyłanie kompletnej reprezentacji Zasobu może być kłopotliwe i wykorzystuje dużo niepotrzebnego pasma. W takich przypadkach, semantyka PATCH ma o wiele więcej sensu.

Innym ważnym aspektem do rozważenia jest idempotencja; PUT jest idempotentny; PATCH może być, ale nie jest wymagane do. I tak – w zależności od semantyki operacji, którą implementujemy, możemy również wybrać jedną lub drugą w oparciu o tę cechę.

Implementing PUT and PATCH Logic

Powiedzmy, że chcemy zaimplementować REST API do aktualizacji HeavyResource z wieloma polami:

public class HeavyResource { private Integer id; private String name; private String address; // ...

Po pierwsze, musimy utworzyć punkt końcowy, który obsługuje pełną aktualizację zasobu za pomocą PUT:

@PutMapping("/heavyresource/{id}")public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved");}

Jest to standardowy punkt końcowy do aktualizacji zasobów.

Teraz, powiedzmy, że pole adresu będzie często aktualizowane przez klienta. W takim przypadku nie chcemy wysyłać całego obiektu HeavyResource ze wszystkimi polami, ale chcemy mieć możliwość aktualizacji tylko pola adresu – poprzez metodę PATCH.

Możemy utworzyć HeavyResourceAddressOnly DTO, aby reprezentować częściową aktualizację pola adresu:

public class HeavyResourceAddressOnly { private Integer id; private String address; // ...}

Następnie, możemy wykorzystać metodę PATCH, aby wysłać częściową aktualizację:

@PatchMapping("/heavyresource/{id}")public ResponseEntity<?> partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated");}

Z tym bardziej ziarnistym DTO, możemy wysłać pole, które musimy tylko zaktualizować – bez narzutu wysyłania całego HeavyResource.

Jeśli mamy dużą liczbę takich częściowych operacji aktualizacji, możemy również pominąć tworzenie własnego DTO dla każdego wyjścia – i użyć tylko mapy:

@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");}

To rozwiązanie da nam więcej elastyczności w implementacji API; jednak stracimy również kilka rzeczy – takich jak walidacja.

Testowanie PUT i PATCH

Na koniec, napiszmy testy dla obu metod HTTP. Po pierwsze, chcemy przetestować aktualizację pełnego zasobu za 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());

Wykonanie częściowej aktualizacji osiągniemy za pomocą metody PATCH:

mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());

Możemy również napisać test dla bardziej ogólnego podejścia:

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

Gdy piszemy implementację dla metody PATCH, musimy określić kontrakt, jak traktować przypadki, gdy otrzymamy null jako wartość dla pola address w HeavyResourceAddressOnly.

Załóżmy, że klient wysyła następujące żądanie:

{ "id" : 1, "address" : null}

Wtedy możemy to potraktować jako ustawienie wartości pola adresu na null lub po prostu zignorować takie żądanie traktując je jako no-change.

Powinniśmy wybrać jedną strategię obsługi null i trzymać się jej w każdej implementacji metody PATCH.

Podsumowanie

W tym krótkim tutorialu skupiliśmy się na zrozumieniu różnic pomiędzy metodami HTTP PATCH i PUT.

Zaimplementowaliśmy prosty kontroler Spring REST do aktualizacji zasobu za pomocą metody PUT i częściowej aktualizacji za pomocą PATCH.

Wdrożenie wszystkich tych przykładów i fragmentów kodu można znaleźć w projekcie na GitHubie – jest to projekt Maven, więc powinien być łatwy do zaimportowania i uruchomienia w takiej postaci.

Zacznij przygodę ze Spring 5 i Spring Boot 2, dzięki kursowi Learn Spring :

>> CHECK OUT THE COURSE

.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.