1. Обзор
В этом кратком руководстве мы рассмотрим различия между HTTP-глаголами PUT и PATCH , а также семантику этих двух операций.
Мы будем использовать Spring для реализации двух конечных точек REST, поддерживающих эти два типа операций, чтобы лучше понять различия и правильный способ их использования.
2. Когда использовать Put и когда 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<String, Object> 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<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());
5. Обработка частичных запросов с нулевыми
значениями
Когда мы пишем реализацию для метода PATCH, нам нужно указать соглашение о том, как обрабатывать случаи, когда мы получаем значение null
в качестве значения для поля адреса в
HeavyResourceAddressOnly
.
Предположим, что клиент отправляет следующий запрос:
{
"id" : 1,
"address" : null
}
Затем мы можем обработать это, установив значение поля адреса
в null
или просто проигнорировав такой запрос, рассматривая его как отсутствие изменений.
Мы должны выбрать одну стратегию для обработки null
и придерживаться ее в каждой реализации метода PATCH.
6. Заключение
В этой быстрой статье мы сосредоточились на понимании различий между методами HTTP PATCH и PUT.
Мы реализовали простой контроллер Spring REST для обновления ресурса с помощью метода PUT и частичного обновления с помощью PATCH.
Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub . Это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.