RunFridge's Blog

PUT vs PATCH 무슨 차이일까

개요

이전에 REST를 공부하면서 HTTP 메소드들에 대해 학습하면서 생긴 의문점이다. RESTful한 서비스를 만들기 위한 제한 사항으로는 일관된 인터페이스 (uniform interface)가 있다.

HTTP 시맨틱을 정의하는 RFC 9110은 활용 가능한 HTTP 메소드를 정의하고 있고, PATCH는 별도의 RFC 5789에서 정의한다.

그 중, PUTPATCH가 둘다 리소스에 대한 "업데이트"를 한다고 표기되어 있는데, 이로인해 두 메소드의 사용 예제에 대한 의문점이 생겼다.

정의

PUT

PUT 정의를 참조하면 다음과 같이 설명한다.

The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message content.

PUT의 특징을 정리하면:

  • 요청 URI에 리소스가 없으면 요청 본문으로 생성
  • 이미 존재하는 리소스인 경우, 요청 본문으로 통째로 교체
  • 따라서 요청 본문은 항상 리소스의 완전한 표현(representation)이어야 한다

PATCH

PATCH 정의를 참조하면 다음과 같이 설명한다.

In a PUT request, the enclosed entity is considered to be a modified version of the resource stored on the origin server, and the client is requesting that the stored version be replaced. With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version.

PATCH의 특징을 정리하면:

  • 요청 본문은 리소스를 "어떻게 수정할지"에 대한 지시(instructions)를 담는다
  • 그 지시를 어떻게 해석할지는 요청의 미디어 타입(Content-Type)이 정한다. 대표적으로 JSON Merge Patch (application/merge-patch+json)와 JSON Patch (application/json-patch+json)가 있다

멱등성 (idempotent)

MDN에서 정의하는 "멱등성"이라함은 다음과 같다.

동일한 요청을 한 번 보내는 것과 여러 번 연속으로 보내는 것이 같은 효과를 지니고, 서버의 상태도 동일하게 남을 때, 해당 HTTP 메서드가 멱등성을 가졌다고 말합니다.

PUT은 리소스를 통째로 교체하므로 정의상 멱등하다. RFC 9110도 PUTDELETE를 멱등한 메소드로 명시한다.

PATCHRFC 5789에서 "safe하지도 idempotent하지도 않다"고 하지만, 곧이어 멱등하게 요청할 수도 있다고 덧붙인다. 즉 PATCH는 멱등성을 보장하지 않을 뿐이고, 실제로 멱등한지는 요청 본문이 어떤 지시를 담고 있는지에 달렸다. 흔히 "PUT은 멱등, PATCH는 비멱등"이라고 외우지만 정확한 표현은 아니다.

예제

간단한 예제로 두 메소드의 차이를 비교해보자. 전제로, 서버는 플레이어의 ID 그리고 위치 좌표(x,y)값을 저장한다고 생각해보자.

데이터베이스에는 다음과 같이 저장되어 있다. 플레이어 ID 1번은 각각 x,y 좌표 (0,0)에 위치해 있다.

{
  "id": 1,
  "x": 0,
  "y": 0
}

PUT /player/1

{
  "id": 1,
  "x": 10,
  "y": 10
}

위 요청은 ID 1번을 가진 player라는 리소스의 내용을 등록 또는 수정하는 요청이다. 해당 플레이어를 좌표 X: 10, Y: 10에 위치되도록 초기화하는 요청이다.

멱등성을 보장해야하기 때문에, 동일한 요청을 반복하여도 플레이어는 (10,10)에 위치한다.

주의할 점은 PUT은 통째로 교체이므로, x만 바꾸고 싶다고 {"x": 20}만 보내면 안 된다. 스펙대로 동작하는 서버라면 리소스가 {"x": 20}으로 교체되어 idy가 사라진다. 일부만 바꾸고 싶다면, 그것이 바로 PATCH가 필요한 이유다.

아래 두 PATCH 예제는 모두 위 PUT 직후 상태인 (10,10)에서 시작한다고 하자.

PATCH /player/1 (멱등한 경우)

PATCH /player/1
Content-Type: application/merge-patch+json

{
  "x": 20
}

JSON Merge Patch에서 위 본문은 "x를 20으로 바꿔라"는 뜻이다. 명시하지 않은 id, y는 건드리지 않는다. 결과는 (20,10)이고, 같은 요청을 반복해도 (20,10)이다. 즉 이 PATCH는 멱등하다.

PATCH /player/1 (멱등하지 않은 경우)

반면 서버가 아래처럼 자체 정의한 "이동" 지시를 받는다고 하자.

PATCH /player/1
Content-Type: application/vnd.game.move+json

{
  "op": "move",
  "dx": 20
}

이 본문은 "현재 위치에서 x를 +20만큼 옮겨라"는 뜻이다. 같은 요청을 반복하면 플레이어는 (30,10), (50,10), (70,10)... 이렇게 계속 이동한다. 이 PATCH는 멱등하지 않다.

정리

  • PUT: 요청 본문은 리소스의 완전한 새 상태. 통째로 교체하므로 항상 멱등.
  • PATCH: 요청 본문은 리소스를 수정하는 지시. 부분 수정이 가능하며, 멱등 여부는 지시의 내용(미디어 타입)에 달렸다.

두 메소드의 본질적인 차이는 "절대값 vs 상대값"이 아니라 "전체 교체 vs 부분 수정"이다.