1. Обзор
В этой краткой статье мы рассмотрим различия между глаголами HTTP PUT и PATCH, а также семантику этих двух операций.
Мы будем использовать Spring для реализации двух конечных точек REST, поддерживающих эти два типа операций, а также для лучшего понимания различий и правильного способа их использования.
2. Когда использовать Put и когда Патч?
Давайте начнем с простого и немного простого утверждения.
Когда клиенту необходимо полностью заменить существующий ресурс, он может использовать PUT. Когда они делают частичное обновление, они могут использовать HTTP-ПАТЧ.
Например, при обновлении одного поля Ресурса отправка полного представления ресурса может быть громоздкой и использовать много ненужной пропускной способности. В таких случаях семантика ПАТЧА имеет гораздо больше смысла.
Еще одним важным аспектом, который следует рассмотреть здесь, является идемпотентность; PUT является идемпотентным; ПАТЧ может быть, но не требуется для . И, таким образом, в зависимости от семантики операции, которую мы реализуем, мы также можем выбрать ту или иную на основе этой характеристики.
3. Реализация логики PUT и PATCH
Допустим, мы хотим реализовать REST API для обновления Тяжелого ресурса с несколькими полями:
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"); }
Это стандартная конечная точка для обновления ресурсов.
Теперь предположим, что поле адреса будет часто обновляться клиентом. В этом случае мы не хотим отправлять весь Тяжелый ресурс объект со всеми полями , но мы хотим иметь возможность обновлять только адрес поле – с помощью метода ИСПРАВЛЕНИЯ.
Мы можем создать Только адрес тяжелого ресурса , чтобы представить частичное обновление поля адреса:
public class HeavyResourceAddressOnly { private Integer id; private String address; // ... }
Затем мы можем использовать метод ИСПРАВЛЕНИЯ для отправки частичного обновления:
@PatchMapping("/heavyresource/{id}") public ResponseEntity> partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated"); }
С помощью этой более детализированной функции мы можем отправить только поле, которое нам нужно обновить, – без накладных расходов на отправку всего HeavyResource .
Если у нас есть большое количество этих операций частичного обновления, мы также можем пропустить создание пользовательского DTO для каждого выхода и использовать только карту:
@RequestMapping(value = "/heavyresource/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> partialUpdateGeneric( @RequestBody Mapupdates, @PathVariable("id") String id) { heavyResourceRepository.save(updates, id); return ResponseEntity.ok("resource updated"); }
Это решение даст нам большую гибкость в реализации API; однако мы также теряем некоторые вещи, такие как проверка.
4. Тестирование ПОСТАВЛЕНО и ИСПРАВЛЕНО
Наконец, давайте напишем тесты для обоих методов 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());
Выполнение частичного обновления достигается с помощью метода ИСПРАВЛЕНИЯ:
mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());
Мы также можем написать тест для более общего подхода:
HashMapupdates = 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. Обработка Частичных Запросов С Нулевыми Значениями
Когда мы пишем реализацию для метода ИСПРАВЛЕНИЯ, нам нужно указать контракт о том, как обрабатывать случаи, когда мы получаем null в качестве значения для поля address в HeavyResourceAddressOnly.
Предположим, что клиент отправляет следующий запрос:
{ "id" : 1, "address" : null }
Затем мы можем обработать это как установку значения поля address в null или просто игнорировать такой запрос, рассматривая его как без изменений.
Мы должны выбрать одну стратегию для обработки null и придерживаться ее в каждой реализации метода ИСПРАВЛЕНИЯ.
6. Заключение
В этом кратком руководстве мы сосредоточились на понимании различий между HTTP-ПАТЧЕМ и методами PUT.
Мы реализовали простой контроллер Spring REST для обновления ресурса с помощью метода PUT и частичного обновления с помощью ПАТЧА.
Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub – это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.