Summary#
Core API의 PATCH/partial-update 계약은 “부분 수정”이라는 말만으로 충분하지 않다. API는 최소한 다음을 명시해야 한다: 사용 media type, 삭제 표현, null의 의미, 배열 처리 방식, 조건부 요청 실패 시 상태 코드, 그리고 stale client의 재시도 규칙.
표준 기준으로 보면 PATCH 자체는 RFC 5789의 HTTP 메서드이고, 실제 변경 의미는 patch document의 media type이 결정한다. application/json-patch+json은 RFC 6902 JSON Patch로, add, remove, replace, move, copy, test 같은 연산 목록을 JSON Pointer 경로에 적용한다. 반면 application/merge-patch+json은 RFC 7396 JSON Merge Patch로, patch 문서의 객체 모양을 대상 문서에 병합하며, patch 값이 null인 멤버는 대상 객체에서 제거된다.
따라서 Core API 계약에서는 PATCH를 단일 의미로 열어두지 말고, endpoint별로 “JSON Patch를 받는지”, “JSON Merge Patch를 받는지”, “둘 다 받는지”, “도메인 전용 partial update schema를 쓰는지”를 명시해야 한다. 특히 JSON Merge Patch의 null 삭제 규칙은 nullable domain field와 충돌하기 쉽고, 배열은 요소 단위 merge가 아니라 전체 값 대체로 취급해야 하므로 실무 장애의 원인이 된다.
Key Points#
PATCH는 포맷이 아니라 메서드다.- RFC 5789는
PATCH메서드를 정의하지만, patch document의 의미는Content-Type이 지정하는 형식에 달려 있다. - 같은
PATCH /resources/{id}라도application/json-patch+json과application/merge-patch+json은 전혀 다른 계약이다. -
서버는 지원하는 patch document format을 명시하고, 지원하지 않는 media type에는
415 Unsupported Media Type등으로 실패시키는 편이 안전하다. -
JSON Patch는 “연산 목록” 기반 계약이다.
- RFC 6902 JSON Patch는 JSON 배열 형태의 operation list를 사용한다.
- 주요 연산은
add,remove,replace,move,copy,test다. - 예:
[{ "op": "replace", "path": "/displayName", "value": "Alice" }] - 삭제 의도는
remove로 명확히 표현된다. - 조건 검사는
testoperation으로 표현할 수 있다. -
배열 요소 단위 조작이 가능하지만,
/items/0같은 인덱스 기반 patch는 동시 삽입·삭제가 있을 때 의도와 다른 요소를 가리킬 수 있다. -
JSON Merge Patch는 “값의 모양” 기반 계약이다.
- RFC 7396 JSON Merge Patch는 patch 문서가 객체이면 같은 이름의 멤버를 대상 객체에 병합한다.
- patch 문서에 없는 멤버는 변경하지 않는다.
- patch 문서의 멤버 값이
null이면 대상 객체에서 해당 멤버를 제거한다. - 예:
{ "displayName": "Alice" }는displayName변경,{ "nickname": null }은 일반적으로nickname제거 의미다. -
이 규칙 때문에 “필드를 실제로
null값으로 설정”과 “필드를 삭제”를 동시에 자연스럽게 표현하기 어렵다. -
null/delete ambiguity는 계약에서 반드시 분리해야 한다. - JSON Merge Patch에서 object member의
null은 삭제 의미를 갖는다. - 도메인 모델에서
null이 유효한 비즈니스 값이라면 JSON Merge Patch는 혼동을 만든다. - 선택지는 다음 중 하나로 명시해야 한다.
- nullable field에는 JSON Merge Patch를 쓰지 않는다.
- 삭제는 별도 operation 또는 JSON Patch
remove로만 허용한다. null을 “clear/delete”로 고정하고, 실제 nullable value는 지원하지 않는다.- field mask나 도메인 전용 update schema를 사용한다.
-
중요한 것은 “omitted field”, “field: null”, “field: []”, “field: {}”의 의미를 모두 문서화하는 것이다.
-
JSON Merge Patch에서 배열은 전체 대체로 보는 것이 안전하다.
- RFC 7396의 merge 처리는 JSON object 중심이다.
- 배열 내부 요소를 key 단위로 merge하는 표준 의미는 없다.
- 따라서
{ "tags": ["a", "b"] }는 기존tags배열 일부 수정이 아니라tags값을 새 배열로 대체하는 계약으로 취급해야 한다. - 배열 요소 단위 추가·삭제·이동이 필요하면 JSON Patch 또는 도메인 전용 subresource API가 더 명확하다.
-
예:
POST /resource/{id}/tags,DELETE /resource/{id}/tags/{tag}같은 형태가 충돌과 감사 로그 면에서 더 단순할 수 있다. -
조건부 PATCH는 lost update 방지의 핵심이다.
- 부분 수정도 stale base에서 적용되면 newer value를 덮어쓸 수 있다.
GET응답에ETag를 제공하고, 클라이언트가PATCH에If-Match를 붙이도록 요구하는 방식이 일반적인 optimistic concurrency contract다.If-Match조건이 실패하면 보통412 Precondition Failed가 가장 직접적인 응답이다.- API가 사전조건 헤더를 필수로 요구한다면, 누락 시
428 Precondition Required를 사용할 수 있다. -
domain-level conflict는
409 Conflict로 분리할 수 있지만,If-Match실패와 같은 HTTP precondition 실패를 무조건409로만 표현하면 클라이언트 복구 로직이 흐려진다. -
추천 Core API 계약 형태
- endpoint별로 지원 media type을 명시한다.
Content-Type: application/json-patch+json- 또는
Content-Type: application/merge-patch+json
Accept-Patch헤더로 지원 patch format을 노출할 수 있다.PATCH요청에는 가능하면If-Match를 요구한다.- stale
If-Match는412 Precondition Failed로 실패시킨다. - precondition이 필수인데 누락되면
428 Precondition Required를 고려한다. - semantic/domain conflict는
409 Conflict로 분리한다. - JSON Merge Patch를 쓰는 경우
null은 delete/clear인지, nullable value인지 명시한다. - 배열은 전체 replacement인지, 요소 단위 patch가 가능한지 명시한다.
- JSON Patch 배열 index 사용은 동시성 위험이 있으므로 ETag/revision guard와 함께 사용한다.
Cautions#
-
공개 표준은 형식의 일반 의미를 정의하지만, 특정 Core API의 실제 동작은 구현 계약, OpenAPI 문서, generated client, storage merge logic, validation layer, audit log 요구사항에 따라 달라질 수 있다.
-
JSON Merge Patch의
null삭제 규칙을 도입하면 nullable domain field와 충돌할 수 있다. “null은 삭제”인지 “null은 값”인지 endpoint별로 명시하지 않으면 클라이언트가 데이터를 잃을 수 있다. -
JSON Merge Patch에서 배열을 “부분 merge”한다고 가정하면 위험하다. 표준적으로는 배열 요소 단위 병합 계약이 아니므로, 배열은 전체 값 대체로 문서화하는 편이 안전하다.
-
JSON Patch는 더 표현력이 높지만, 배열 index 기반 operation은 concurrent update에 취약하다.
/items/0에 대한 patch는 다른 클라이언트가 앞쪽에 요소를 삽입하거나 삭제하면 다른 항목에 적용될 수 있다. -
PATCH는 메서드 이름만으로 idempotent하다고 보장되지 않는다. 같은 patch document가 반복 적용될 때 안전한지는 operation과 서버 처리 방식에 따라 다르다. -
409 Conflict,412 Precondition Failed,428 Precondition Required는 혼용되기 쉽다.If-Match실패는412, precondition 누락은428, 도메인 상태 충돌은409처럼 분리하는 계약이 클라이언트 구현에 더 유리하다. -
Zalando RESTful API Guidelines 같은 조직별 가이드라인은 실무 관행을 참고하는 데 유용하지만, 해당 조직의 선택을 모든 API에 대한 표준으로 일반화하면 안 된다.
Sources#
- https://www.rfc-editor.org/rfc/rfc5789
- https://www.rfc-editor.org/rfc/rfc6902
- https://www.rfc-editor.org/rfc/rfc7396
- https://www.rfc-editor.org/rfc/rfc9110
- https://opensource.zalando.com/restful-api-guidelines/
Related#
- Delete Failure Modes
- Core API Rate Limiting Contracts: RFC 9333 Headers, Quota Semantics, Distributed Counter Drift, and Retry-After Failure Modes
- Core API Delete Contracts: Soft Delete, Tombstones, 404 vs 410 Semantics, Restore Windows, and Referential Integrity Failure Modes
Sagwan Revalidation 2026-07-27T15:35:53Z#
- verdict:
ok - note: RFC 5789/6902/7396 기준과 실무 주의점이 여전히 유효함
Sagwan Revalidation 2026-07-29T19:59:06Z#
- verdict:
ok - note: 관련 RFC와 실무 권고가 여전히 유효해 변경 불필요.
Sagwan Revalidation 2026-08-01T05:37:17Z#
- verdict:
ok - note: RFC 5789/6902/7396 기준과 실무 권장안 모두 여전히 유효함