HTTP PUT vs HTTP PATCH in a REST API

przegląd

w tym krótkim artykule przyjrzymy się różnicom mię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 rodzaje operacji, oraz aby lepiej zrozumieć różnice i właściwy sposób ich użycia.

kiedy stosować Put a kiedy Patch?

Zacznijmy od prostego i nieco prostego stwierdzenia.

gdy klient musi całkowicie zastąpić istniejący zasób, może użyć PUT. Kiedy robią częściową aktualizację, mogą użyć poprawki HTTP.

na przykład, podczas aktualizacji pojedynczego pola zasobu, wysłanie pełnej reprezentacji zasobu może być uciążliwe i wykorzystuje dużo niepotrzebnej przepustowości. W takich przypadkach semantyka patcha ma o wiele większy sens.

innym ważnym aspektem do rozważenia jest idempotencja; PUT jest idempotentem; PATCH może być, ale nie musi. I tak – w zależności od semantyki operacji, którą realizujemy, możemy również wybrać jedną lub drugą na podstawie tej cechy.

implementacja logiki PUT I PATCH

powiedzmy, że chcemy zaimplementować REST API do aktualizacji ciężkiego źródła z wieloma polami:

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

najpierw 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.

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 tylko możliwość aktualizacji pola adresu-za pomocą metody PATCH.

możemy utworzyć HeavyResourceAddressOnly DTO reprezentujące częściową aktualizację pola adresu:

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

następnie możemy użyć metody 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 szczegółowym DTO, możemy wysłać pole, które musimy zaktualizować tylko-bez narzutu wysyłania całego HeavyResource.

jeśli mamy dużą liczbę tych częściowych operacji aktualizacji, możemy również pominąć Tworzenie niestandardowego DTO dla każdego out-i używać 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ększą elastyczność we wdrażaniu API, jednak tracimy 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 odbywa się przy użyciu 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 na bardziej ogólne podejście:

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());

obsługa żądań częściowych o wartościach Null

kiedy piszemy implementację dla metody PATCH, musimy określić kontrakt, w jaki sposób traktować przypadki, gdy otrzymamy null jako wartość dla pola adresu w HeavyResourceAddressOnly.

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

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

następnie możemy obsłużyć to jako ustawienie wartości pola adresu na null lub po prostu ignorowanie takiego żądania, traktując je jako bez zmian.

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

podsumowanie

w tym krótkim samouczku skupiliśmy się na zrozumieniu różnic między HTTP PATCH i metodami PUT.

zaimplementowaliśmy prosty kontroler Spring REST do aktualizacji zasobu za pomocą metody PUT i częściowej aktualizacji za pomocą łatki.

implementacja wszystkich tych przykładów i fragmentów kodu znajduje się w projekcie GitHub – jest to projekt Maven, więc powinien być łatwy do zaimportowania i uruchomienia w takim stanie, w jakim jest.

zacznij od Spring 5 i Spring Boot 2, korzystając z kursu Learn Spring :

>> sprawdź kurs

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.