HTTP PUT срещу HTTP PATCH в REST API

1. Общ преглед

В тази кратка статия разглеждаме разликите между глаголите HTTP PUT и PATCH и семантиката на двете операции.

Ще използваме Spring, за да реализираме две крайни точки REST, които поддържат тези два вида операции, и за да разберем по-добре разликите и правилния начин да ги използваме.

2. Кога да използвам Put и When Patch?

Нека започнем с просто и малко просто изявление.

Когато клиентът трябва да замени изцяло съществуващ Ресурс, той може да използва PUT. Когато правят частична актуализация, те могат да използват HTTP PATCH.

Например, когато актуализирате едно поле на Ресурса, изпращането на пълното представяне на Ресурса може да е тромаво и да използва много ненужна честотна лента. В такива случаи семантиката на PATCH има много по-голям смисъл.

Друг важен аспект, който трябва да се разгледа тук, е идемпотентност; PUT е идемпотентен; PATCH може да бъде, но не се изисква . И така - в зависимост от семантиката на операцията, която изпълняваме, ние също можем да изберем едното или другото въз основа на тази характеристика.

3. Внедряване на PUT и PATCH логика

Да предположим, че искаме да приложим REST API за актуализиране на HeavyResource с множество полета:

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

Първо, трябва да създадем крайната точка, която обработва пълна актуализация на ресурса, използвайки PUT:

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

Това е стандартна крайна точка за актуализиране на ресурси.

Да кажем, че адресното поле често се актуализира от клиента. В този случай не искаме да изпращаме целия обект HeavyResource с всички полета , но искаме възможността да актуализираме само полето за адрес - чрез метода PATCH.

Можем да създадем HeavyResourceAddressOnly DTO, за да представим частична актуализация на адресното поле:

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

След това можем да използваме метода PATCH, за да изпратим частична актуализация:

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

С този по-гранулиран DTO можем да изпратим полето, което трябва да актуализираме, без да се налага да изпращаме цели HeavyResource .

Ако имаме голям брой от тези частични операции по актуализиране, можем също да пропуснем създаването на персонализиран DTO за всеки изход - и да използваме само карта:

@RequestMapping(value = "/heavyresource/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity partialUpdateGeneric( @RequestBody Map updates, @PathVariable("id") String id) { heavyResourceRepository.save(updates, id); return ResponseEntity.ok("resource updated"); }

Това решение ще ни даде по-голяма гъвкавост при внедряването на API; губим обаче и няколко неща - като валидиране.

4. Тестване на PUT и PATCH

И накрая, нека напишем тестове и за двата HTTP метода. Първо искаме да тестваме актуализацията на пълния ресурс чрез метода 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());

Изпълнението на частична актуализация се постига чрез метода PATCH:

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

Можем да напишем и тест за по-общ подход:

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

5. Обработка на частични заявки с нулеви стойности

Когато пишем реализация за метод PATCH, трябва да посочим договор за това как да се третират случаи, когато получаваме null като стойност за адресното поле в HeavyResourceAddressOnly.

Да предположим, че клиентът изпраща следната заявка:

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

Тогава можем да се справим с това като задаване на стойност на полето за адрес на нула или просто като игнорираме такава заявка, като я третираме като без промяна.

Трябва да изберем една стратегия за обработка на null и да се придържаме към нея при всяко внедряване на метода PATCH.

6. Заключение

В този бърз урок се фокусирахме върху разбирането на разликите между методите HTTP PATCH и PUT.

Внедрихме прост контролер Spring REST, за да актуализираме ресурс чрез метода PUT и частично актуализиране с помощта на PATCH.

Изпълнението на всички тези примери и кодови фрагменти може да се намери в проекта GitHub - това е проект на Maven, така че трябва да е лесно да се импортира и да се изпълнява както е.