Post

Spring REST API PUT vs HTTP PATCH

1. Put을 사용해야 하는 경우와 Patch를 사용해야 하는 경우

클라이언트가 기존 리소스를 완전히 교체해야 하는 경우 PUT을 사용할 수 있다. 부분 업데이트를 수행할 때 HTTP PATCH를 사용할 수 있다.

예를 들어 리소스의 단일 필드를 업데이트할 때 전체 리소스 표현을 보내는 것은 번거로울 수 있으며 불필요한 대역폭을 많이 사용한다. 이러한 경우 PATCH의 의미 체계가 훨씬 더 의미가 있다.

고려해야 할 또 다른 중요한 측면은 멱등성입니다. PUT은 멱등적이다. PATCH는 멱등적일 수 있지만 반드시 그럴 필요는 없다. 따라서 우리가 구현하는 작업의 의미 체계에 따라 이 특성에 따라 둘 중 하나를 선택할 수도 있다.

2. PUT 및 PATCH 로직 구현

여러 필드가 있는 HeavyResource를 업데이트하기 위해 REST API를 구현한다고 가정한다.

1
2
3
4
5
public class HeavyResource {
    private Integer id;
    private String name;
    private String address;
    // ...

먼저 PUT을 사용하여 리소스의 전체 업데이트를 처리하는 엔드포인트를 생성해야 한다.

1
2
3
4
5
6
@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를 만들 수 있다.

1
2
3
4
5
6
public class HeavyResourceAddressOnly {
    private Integer id;
    private String address;
    
    // ...
}

다음으로 PATCH 메서드를 활용하여 부분 업데이트를 보낼 수 있다.

1
2
3
4
5
6
7
@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 생성을 건너뛰고 맵만 사용할 수도 있다.

1
2
3
4
5
6
7
8
@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 구현에 더 많은 유연성을 제공하지만 유효성 검사와 같은 몇 가지 사항도 잃게 된다.

3. PUT 및 PATCH 테스트

두 HTTP 메서드에 대한 테스트이다.

먼저 PUT 메서드를 통해 전체 리소스의 업데이트를 테스트하려고 한다.

1
2
3
4
5
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 방법을 사용하여 수행된다.

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

보다 일반적인 접근 방식에 대한 테스트를 작성할 수도 있다.

1
2
3
4
5
6
7
8
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());
 

4. Null 값이 있는 부분 요청 처리

PATCH 메서드에 대한 구현을 작성할 때 HeavyResourceAddressOnly의 주소 필드에 대한 값이 null일 때 처리하는 방법이다.

클라이언트가 다음 요청을 보낸다고 가정합니다.

1
2
3
4
{
   "id" : 1,
   "address" : null
}

그런 다음 주소 필드의 값을 null로 설정 하거나 변경 사항이 없는 것으로 처리하여 이러한 요청을 무시하는 것으로 처리할 수 있다.

null을 처리하기 위한 하나의 전략을 선택 하고 모든 PATCH 메서드 구현에서 이를 고수해야 한다.

[출처 및 참고]

This post is licensed under CC BY 4.0 by the author.