//////

Core API Partial Update Contracts: JSON Merge Patch, JSON Patch, Field Masks, and Null/Delete Failure Modes

Core API의 partial update 계약은 단순히 “PATCH를 쓴다”로 끝나지 않는다. 최소한 JSON Merge Patch(RFC 7396) , JSON Patch(RFC 6902) , FieldMask/updateMask 계열 중 어떤 모델을 채택하는지 명시해야 하며, 특히 다음을 계약에 고정해야 한다. - omitted field는 “변경 없음”인가? - null은 “값을 null로 설정”인가, “필드 삭제/clear”인가? - 배열은 교체되는가

//////

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 또는 add value로 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는 409412의 의미를 구분해야 한다.

  • 권장 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 Conflict412 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

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 관행 모두 현재도 유효하다.

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1