Học viên: Lương Quốc
Lớp: Java Fulltack 15
Email: quoc200799@gmail.com
Link tham khảo: https://www.baeldung.com/http-put-patch-difference-spring


1. Tổng quan

Trong bài viết này, chúng ta đang xem xét sự khác biệt giữa các hai phương thức HTTP PUT và PATCH trong REST API.

Chúng ta sẽ sử dụng Spring để triển khai hai điểm cuối REST hỗ trợ hai loại thao tác này nhằm hiểu rõ hơn về sự khác biệt và cách đúng đắn để sử dụng chúng.

2. Khi nào sử dụng PUT và khi nào sử dụng PATCH

Khi client cần thay thế toàn bộ resource hiện có họ sẽ sử dụng PUT. Cũng giống như PUT, PATCH được dùng để thay đổi data, thế nhưng nó chỉ thay đổi những field được yêu cầu thay đổi thay vì toàn bộ resource.

Cho ví dụ, khi ta cần cập nhật một trường duy nhất của tài nguyên mà không cần sử dụng toàn bộ dữ liệu trong tài nguyên đó gây cồng kềnh và tốn băng thông không cần thiết. Trong trường hợp này, chúng ta sử dụng PATCH sẽ khả dụng hơn.

Ở một khía cạnh quan trọng chúng ta cần so sánh ở đây là idempotence(“idempotent” là một hoạt động mà không ảnh hưởng đến ứng dụng mà nó được gọi bên trong, nếu nó được gọi là nhiều hơn một lần với các thông số đầu vào tương tự. Các loại hoạt động thay đổi cấu trúc dữ liệu của ứng dụng với mỗi lần lặp.). PUT là idempotence, PATCH có là idempotence nhưng không bắt buộc. Nên, tùy thuộc vào vào ngữ cảnh chúng ta có thể chọn PATCH hoặc PUT dựa trên idempotence.

3. Triển khai Logic cho PUT và PATCH

Giả sử chúng ta muốn triển khai REST API để cập nhật class HeavyResource với nhiều trường:

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

Đầu tiên, chúng ta cần tạo Controller để xử lý cập nhật tất cả tài nguyên nên sử dụng PUT

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

Bây giờ, giả sử rằng trường address sẽ thường xuyên được cập nhật bởi client. Trong trường hợp đó, chúng tôi không muốn gửi toàn bộ đối tượng HeavyResource với tất cả các trường, nhưng chúng tôi muốn chỉ cập nhật trường địa chỉ — thông qua method PATCH.

Chúng ta có thể tạo class HeavyResourceAddressOnly DTO để thể hiện một phần cập nhật của trường address:

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

Tiếp theo, chúng ta có thể dùng phương thức PATCH để cập nhật một phần :

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

Với DTO chi tiết hơn này, chúng tôi chỉ có thể gửi trường chúng tôi cần cập nhật mà không phải gửi toàn bộ class HeavyResource.

Nếu chúng tôi có một số lượng lớn các hoạt động cập nhật từng phần này, chúng tôi cũng có thể bỏ qua việc tạo DTO — và chỉ sử dụng Map:

@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");
}

Giải pháp này sẽ giúp chúng ta linh hoạt hơn trong việc truển khai API, nhưng chúng ta cũng sẽ mất một số thứ, chẳng hạn như validation.

4. Thử nghiệm dùng PUT và PATCH

Cuối cùng, Hãy cố thử dùng cả 2 phương thức HTTP.

Đầu tiên, chúng ta muốn cập nhật toàn bộ dữ liệu thông qua phương thức 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());

Còn việc thực hiện cập nhật một phần cần sử dụng phương thức PATCH:

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

Chúng ta có thể viết một bài kiểm tra cho một cách chung chung hơn:

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. Xử lý một phần Requests với Gái trị Null.

Khi chúng tôi đang viết triển khai cho phương thức PATCH, chúng tôi cần chỉ định về cách xử lý trường hợp khi nhận giá trị null làm giá trị cho trường address trong HeavyResourceAddressOnly.

Giả sử rằng client gửi yêu cầu sau:

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

Sau đó, chúng ta có thể xử lý việc này như đặt giá trị của trường address thành null hoặc chỉ bỏ qua yêu cầu đó bằng cách coi nó là không thay đổi.

Chúng ta nên chọn một cách để xử lý null và tuân theo nó trong mọi triển khai phương thức PATCH

6. Kết luận

Trong bài viết này, chúng tôi tập trung vào việc tìm hiểu sự khác biệt giữa các phương thức HTTP PATCH và PUT.

Chúng tôi đã triển khai một Spring REST controller đơn giản để cập nhật toàn bộ dữ liệu thông qua phương thức PUT và cập nhật một phần bằng PATCH.

Việc triển khai tất cả các ví dụ và code ở bài này có thể được tìm thấy trong GitHub project. Đây là một dự án Maven.

Cảm ơn bạn đã đọc bài viết của tôi.