Summary#
Core API의 partial update 계약은 단순히 “PATCH를 쓴다”로 끝나지 않는다. 최소한 JSON Merge Patch(RFC 7396), JSON Patch(RFC 6902), FieldMask/updateMask 계열 중 어떤 모델을 채택하는지 명시해야 하며, 특히 다음을 계약에 고정해야 한다.
- omitted field는 “변경 없음”인가?
null은 “값을 null로 설정”인가, “필드 삭제/clear”인가?- 배열은 교체되는가, 요소 단위로 수정되는가?
- 필드 삭제, 기본값 clear, nested object merge는 어떻게 표현하는가?
- stale client가 partial update를 보내면 lost update를 어떻게 막는가?
ETag/If-Match/ revision mismatch 실패는412 Precondition Failed또는409 Conflict중 무엇으로 표현하는가?
실무적으로는 문서형 리소스에 단순 부분 갱신이 필요하면 JSON Merge Patch, 정확한 연산 목록·배열 요소 조작·조건부 테스트가 필요하면 JSON Patch, protobuf/Google-style API처럼 resource body와 updateMask를 분리하는 설계라면 FieldMask가 적합하다. 어느 방식을 쓰든 concurrent write 방어에는 If-Match 또는 revision token 기반 optimistic concurrency가 필요하다.
Key Points#
- JSON Merge Patch는 “값의 모양으로 변경 의도를 표현”한다.
- patch document가 JSON object이면 같은 이름의 멤버를 대상 문서에 병합한다.
- patch에 없는 멤버는 변경하지 않는다.
- patch의 멤버 값이
null이면 대상 객체에서 해당 멤버를 제거한다. - 따라서 object field에서 “실제 값으로서의 null”과 “삭제 의도”를 동시에 자연스럽게 표현하기 어렵다.
-
배열은 요소 단위 merge가 아니라 값 단위로 대체되는 것으로 보는 것이 안전하다.
-
JSON Patch는 “연산 목록으로 변경 의도를 표현”한다.
add,remove,replace,move,copy,test연산을 JSON Pointer 경로에 적용한다.- 삭제는
remove, 값 변경은replace, 조건 검사는test로 표현한다. - 배열 인덱스 단위 조작이 가능하지만, 인덱스 기반 patch는 동시 수정에 취약하다.
-
test연산은 patch 내부의 조건부 갱신에 도움을 줄 수 있지만, HTTP-level lost update 방어를 완전히 대체한다고 단정하면 안 된다. -
FieldMask/updateMask는 “어떤 필드를 갱신 대상으로 볼지”를 body와 별도로 명시한다.
- Google API 설계 관행에서는 update 요청에 resource와
update_mask를 함께 보낸다. - mask에 포함된 필드만 갱신 대상으로 해석하고, 포함되지 않은 필드는 보존한다.
- 필드를 기본값 또는 empty value로 clear하려면 해당 필드를 mask에 포함하고 값은 clear 상태로 보낸다는 식의 계약이 필요하다.
- protobuf FieldMask 문서는 update operation에서 mask가 갱신할 필드 subset을 지정하며, repeated field는 일반적으로 path의 마지막 위치에만 올 수 있다고 설명한다.
-
FieldMask는
null의 의미가 애매한 JSON Merge Patch보다 typed API와 codegen에 잘 맞지만, default value와 presence semantics를 명확히 문서화해야 한다. -
null/ omitted / delete semantics는 API 표면에서 가장 위험한 부분이다. - JSON Merge Patch:
- omitted: 변경 없음
field: null: 필드 제거field: value: 값 설정 또는 object merge
- JSON Patch:
- omitted 개념 없음; operation list가 전부
- 삭제:
remove - null 설정:
replace또는addvalue로null사용 가능
-
FieldMask:
- mask에 없음: 변경 없음
- mask에 있음 + 값 제공: 해당 값으로 갱신
- mask에 있음 + default/empty/null 표현: clear 또는 default 설정으로 해석될 수 있으나 API별 presence 규칙 필요
-
concurrent write 실패 모드를 별도 계약으로 다뤄야 한다.
- partial update는 전체 replace보다 안전해 보이지만 lost update를 자동으로 막지 않는다.
- 예: 클라이언트 A와 B가 같은 base revision을 읽고 서로 다른 field를 patch하면 병합 가능할 수 있다.
- 반대로 같은 nested object, 같은 배열, 또는 서버 계산 필드를 동시에 변경하면 마지막 쓰기가 앞선 쓰기를 덮을 수 있다.
- HTTP 조건부 요청에서는
If-Match가 현재 representation의 ETag와 맞을 때만 요청을 적용하도록 할 수 있다. If-Match조건이 실패하면 HTTP 의미론상412 Precondition Failed가 직접적이다.-
도메인 충돌이나 병합 불가능 상태를 표현할 때는
409 Conflict도 사용할 수 있으나, API는409와412의 의미를 구분해야 한다. -
권장 core-api 계약 문구
- “이 endpoint는
application/merge-patch+json만 허용한다” 또는 “application/json-patch+json만 허용한다”처럼 media type을 명시한다. - PATCH body schema에
null의미를 필드별로 적는다. - delete가 필요한 필드는 delete 표현을 별도로 제공한다.
- 배열 필드는 “전체 교체”인지 “요소 연산”인지 명시한다.
- 모든 partial update는
If-Match또는revision을 요구한다. - stale write는
412 Precondition Failed로 응답하고, 응답 body에 latest revision/ETag 또는 재조회 링크를 제공한다. - 병합 불가능한 domain conflict는
409 Conflict로 분리한다. - client는 stale partial update를 blind retry하지 말고 latest resource를 다시 읽은 뒤 rebase해야 한다.
Cautions#
- JSON Merge Patch의
null삭제 규칙은 nullable domain field와 충돌하기 쉽다. nullable 값을 실제 비즈니스 값으로 다뤄야 하는 API에는 JSON Patch나 FieldMask가 더 명확할 수 있다. - FieldMask의 clear/default 동작은 protobuf presence, proto2/proto3, wrapper type, optional field, REST transcoding 방식에 따라 구현 차이가 생길 수 있다.
- JSON Patch의 배열 인덱스 조작은 동시 삽입/삭제가 있을 때 의도와 다른 요소를 수정할 수 있다. 배열 patch에는 ETag/revision 또는 domain-level item id가 필요하다.
PATCH자체는 idempotent하다고 보장되지 않는다. 특정 patch document가 idempotent인지 여부는 사용한 operation과 서버 처리 방식에 달려 있다.409 Conflict와412 Precondition Failed는 혼용되기 쉽다.If-Match실패는 보통412가 더 직접적이고, domain conflict는409로 분리하는 것이 해석 가능성이 높다.- 공개 표준만으로 특정 조직의 API 스타일을 일반화하면 안 된다. 실제 core-api 계약은 media type, schema, generated client, storage merge logic, audit log 요구사항을 함께 검증해야 한다.
Sources#
- https://www.rfc-editor.org/rfc/rfc5789
- https://www.rfc-editor.org/rfc/rfc7396
- https://www.rfc-editor.org/rfc/rfc6902
- https://www.rfc-editor.org/rfc/rfc9110
- https://google.aip.dev/134
- https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask
Related#
- Core API Conditional Mutation Contracts: ETag/If-Match, 412 vs 428, Lost-Update Prevention, and Version Drift Failure Modes
- Core API Idempotency-Key Contracts: Request Fingerprinting, Replay Semantics, Concurrent Duplicate Suppression, and Expiry Failure Modes
- Core API Long-Running Operation Contracts: 202 Accepted, Operation Resources, Cancellation, and Duplicate-Submit Failure Modes
Sagwan Revalidation 2026-07-12T19:50:47Z#
- verdict:
ok - note: RFC와 관행 모두 현재도 유효하며 재사용에 문제 없어 보임
Sagwan Revalidation 2026-07-14T16:52:24Z#
- verdict:
ok - note: RFC와 Google-style updateMask 관행 모두 여전히 유효하다.
Sagwan Revalidation 2026-07-16T17:10:01Z#
- verdict:
ok - note: RFC와 Google-style updateMask 관행 모두 현재도 유효하다.
Sagwan Revalidation 2026-07-18T18:40:18Z#
- verdict:
ok - note: RFC와 Google FieldMask 관행 모두 안정적이라 현재도 재사용 가능
Sagwan Revalidation 2026-07-20T19:51:22Z#
- verdict:
ok - note: RFC와 Google-style updateMask 관행 모두 현재도 유효하다.