HTTP PUT vs HTTP PATCH in a REST API – REST API中的HTTP PUT vs HTTP PATCH

最后修改: 2017年 3月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this quick tutorial, we’re looking at differences between the HTTP PUT and PATCH verbs and at the semantics of the two operations.

在这个快速教程中,我们将研究HTTP PUT和PATCH动词之间的差异以及这两种操作的语义。

We’ll use Spring to implement two REST endpoints that support these two types of operations in order to better understand the differences and the right way to use them.

我们将使用Spring实现两个支持这两类操作的REST端点,以便更好地理解它们的区别和正确的使用方法。

2. When to Use Put and When Patch?

2.什么时候用放,什么时候用补?

Let’s start with both a simple and a slightly simple statement.

让我们从一个简单的和略微简单的声明开始。

When a client needs to replace an existing Resource entirely, they can use PUT. When they’re doing a partial update, they can use HTTP PATCH.

当客户需要完全替换一个现有的资源时,他们可以使用PUT。当他们进行部分更新时,他们可以使用HTTP PATCH.

For instance, when updating a single field of the Resource, sending the complete Resource representation can be cumbersome and uses a lot of unnecessary bandwidth. In such cases, the semantics of PATCH make a lot more sense.

例如,当更新资源的一个字段时,发送完整的资源表示可能很麻烦,而且会使用大量不必要的带宽。在这种情况下,PATCH的语义就有很大的意义。

Another important aspect to consider here is idempotence. PUT is idempotent; PATCH can be idempotent but isn’t required to be. So, depending on the semantics of the operation we’re implementing, we can also choose one or the other based on this characteristic.

这里需要考虑的另一个重要方面是empotence。PUT是empotent;PATCH可以是empotent,但不需要是empotent。因此,根据我们要实现的操作的语义,我们也可以根据这个特性选择一个或另一个。

3. Implementing PUT and PATCH Logic

3.实现PUT和PATCH逻辑

Let’s say we want to implement the REST API for updating a HeavyResource with multiple fields:

假设我们想实现REST API来更新一个有多个字段的HeavyResource

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

First, we need to create the endpoint that handles a full update of the resource using PUT:

首先,我们需要创建一个端点,用PUT来处理资源的全面更新。

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

This is a standard endpoint for updating resources.

这是一个用于更新资源的标准端点。

Now let’s say that address field will often be updated by the client. In that case, we don’t want to send the whole HeavyResource object with all fields, but we do want the ability to only update the address field — via the PATCH method.

现在,让我们假设该地址字段将经常被客户更新。在这种情况下,我们不想发送包含所有字段的整个HeavyResource对象,但是我们确实希望能够只更新address字段–通过 PATCH 方法。

We can create a HeavyResourceAddressOnly DTO to represent a partial update of the address field:

我们可以创建一个HeavyResourceAddressOnlyDTO来表示地址字段的部分更新。

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

Next, we can leverage the PATCH method to send a partial update:

接下来,我们可以利用PATCH方法来发送一个部分更新。

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

With this more granular DTO, we can send the field we need to update only, without the overhead of sending the whole HeavyResource.

有了这个更细化的DTO,我们可以只发送我们需要更新的字段,而不用发送整个HeavyResource的开销。

If we have a large number of these partial update operations, we can also skip the creation of a custom DTO for each out — and only use a map:

如果我们有大量这样的部分更新操作,我们也可以跳过为每一个出去的人创建一个自定义的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");
}

This solution will give us more flexibility in implementing API, but we do lose a few things as well, such as validation.

这个解决方案将使我们在实现API方面有更大的灵活性,但我们也确实失去了一些东西,比如验证。

4. Testing PUT and PATCH

4.测试PUT和PATCH

Finally, let’s write tests for both HTTP methods.

最后,让我们为两种HTTP方法编写测试。

First, we want to test the update of the full resource via PUT method:

首先,我们要测试通过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());

Execution of a partial update is achieved by using the PATCH method:

部分更新的执行是通过使用PATCH方法实现的。

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

We can also write a test for a more generic approach:

我们也可以写一个测试,以获得更通用的方法。

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. Handling Partial Requests With Null Values

5.处理带有Null值的部分请求

When we are writing an implementation for a PATCH method, we need to specify a contract of how to treat cases when we get null as a value for the address field in the HeavyResourceAddressOnly.

当我们为 PATCH 方法编写实现时,我们需要指定一个契约,说明当我们在 HeavyResourceAddressOnly 中得到 null 作为 address 字段的值时如何处理。

Suppose that client sends the following request:

假设客户发送了以下请求。

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

Then we can handle this as setting a value of the address field to null or just ignoring such a request by treating it as no-change.

然后,我们可以将其处理为将address字段的值设置为null,或者直接将其视为无变化而忽略这种请求。

We should pick one strategy for handling null and stick to it in every PATCH method implementation.

我们应该选择一种策略来处理null ,并在每个PATCH方法的实现中坚持使用它。

6. Conclusion

6.结论

In this quick article, we focused on understanding the differences between the HTTP PATCH and PUT methods.

在这篇快速文章中,我们重点了解了HTTP PATCH和PUT方法之间的区别。

We implemented a simple Spring REST controller to update a Resource via PUT method and a partial update using PATCH.

我们实现了一个简单的Spring REST控制器,通过PUT方法更新一个资源,并使用PATCH进行部分更新。

The implementation of all these examples and code snippets can be found in the GitHub project. This is a Maven project, so it should be easy to import and run as it is.

所有这些例子和代码片段的实现都可以在GitHub项目中找到。这是一个Maven项目,所以应该很容易导入并按原样运行。