--- title: API Evolution & Schema (compatibility + serialization + HTTP contract surface) source_type: llm-generated status: reviewed confidence: medium tags: [api-design, versioning, schema, deprecation, pagination, conditional-request, http-cache] related_projects: [ca-skeleton] last_reviewed: 2026-06-04 --- # API Evolution & Schema (compatibility + serialization) > Layer: `wiki/concepts/` — 일반 개념. 특정 프로젝트의 결정/구현 사실은 `wiki/projects/`에서 다룬다. ## Summary API evolution은 두 축으로 나뉜다. (1) **compatibility / deprecation** — 응답 필드 제거나 의미 변화를 막기 위해 breaking change를 분류하고 migration window 동안 deprecated marker와 Sunset 헤더로 client에게 신호를 보낸다. (2) **schema / serialization** — date·money·enum·null·unknown field의 의미를 framework default에 맡기지 않고 명시 계약으로 고정한다. 대표 결정 라인업은 `90d public + 30d internal migration window`, RFC 8594 `Sunset` 헤더, ISO-8601 offset datetime (UTC default), `BigDecimal` scale 2 + `HALF_UP`, **strict inbound / tolerant outbound** 정책이다. ## Standard (공식 정의) ### Compatibility / deprecation 표준 후보 - **RFC 8594 Sunset header (IETF)**: 응답 헤더로 자원이 응답 불가가 될 시점을 HTTP-date로 알린다. `Sunset` 단독은 *언제* 사라지는지 신호일 뿐이고, deprecation 자체는 별도 `Deprecation` 헤더(IETF draft)로 표시하는 것이 표준 의도다. - **Microsoft REST API versioning policy**: `api-version` query/header를 정식 권고. major version 단위 breaking change 허용, minor/preview는 additive only. preview API는 별도 lifecycle. - **GitHub REST API**: 2022년부터 `X-GitHub-Api-Version: YYYY-MM-DD` 날짜 헤더. 새 버전 release 후 **24개월 EOL** 정책, EOL된 버전 호출은 `410 Gone` 응답. preview API는 `Accept` 헤더 `application/vnd.github.-preview+json`로 옵트인. - **Stripe date-based versioning**: account마다 첫 호출 시 version pin. 이후 새 version이 나와도 client가 명시적으로 upgrade하지 않으면 **freeze forever** (Stripe가 영구적으로 구버전 응답을 유지). 외부 컨슈머 규모가 큰 결제 도메인 특화. - **Google AIP-180 (Backwards compatibility)**: enum value 제거 / 의미 변경 / 응답 필드 제거 / 기본값 변경 / required request field 추가 모두 breaking으로 분류. additive (optional response field 추가)만 minor에 허용. - **Twitter tier-based**: legacy / current / beta 트랙 병렬 운영. - **Spring HATEOAS**: 응답에 `_links`로 다음 자원 URI를 동봉해 client가 version이 아닌 link relation에 결합하게 한다. ### Schema / serialization 표준 후보 - **ISO-8601**: date·time·datetime·duration의 wire 표현 표준. offset datetime(`2026-05-22T11:30:00+09:00` 또는 `Z`)이 timezone ambiguity 회피의 정석. - **JSON Schema** (draft 2020-12): JSON payload의 shape 검증 spec. `additionalProperties: false`로 unknown field strict, `nullable` / `required` / `enum`으로 의미 분리. - **OpenAPI 3.1**: JSON Schema 2020-12 정합. response shape SSOT 후보. `deprecated: true` 플래그를 schema/operation 양쪽에 둘 수 있어 deprecation marker 표준 위치가 된다. - **Avro schema evolution**: backward / forward / full compatibility를 schema registry가 자동 검사. 필드 추가/삭제 시 default 의무, alias로 rename. event/outbox 환경에 우위. - **Protobuf**: `reserved` 키워드로 field number와 name 재사용을 영구 차단. wire-format 기반 strict typing. - **Jackson** (Java): `DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES`는 default `true`. 단, `FAIL_ON_NULL_FOR_PRIMITIVES`는 default `false`라 null/missing primitive가 묵시적으로 0이 된다. 출력측은 `SerializationFeature.WRITE_DATES_AS_TIMESTAMPS`(default `false` → `JavaTimeModule` 경유 ISO-8601 문자열, `true` 면 epoch/배열)와 `JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN`(default `false` → 큰 값이 지수 표기 `1.23E+10`)이 wire 형식을 좌우한다. 이 둘은 *프레임워크 기본값*이라 버전 업그레이드로 flip 될 수 있으므로 계약을 명시 핀하고 effective bean 동작 테스트로 회귀를 잡는 것이 안전하다. - **Property naming strategy**: Jackson `PropertyNamingStrategies`(camelCase default / `SNAKE_CASE` / `KEBAB_CASE`)는 wire 의 field 이름 컨벤션을 결정한다. 한 번 정하면 client 가 그 이름에 결합하므로 *변경 자체가 breaking* — 전역 strategy 변경은 모든 응답 field rename 과 동치다. - **Null vs absent (`@JsonInclude`)**: `JsonInclude.Include.NON_NULL`/`NON_ABSENT`/`NON_EMPTY` 는 null 또는 빈 값을 출력에서 *생략* 한다. 생략(absent)과 명시적 `null` 은 client 에게 다른 의미(부재 vs 값이 null) 일 수 있어, JSON Merge Patch 같은 부분 갱신 의미가 필요하면 `JsonNullable` 로 3-상태(present-null / present-value / absent)를 구분한다. - **Java BigDecimal**: 금액 계산 표준. `new BigDecimal(double)` 함정 (`0.1` → `0.1000000000000000055511151231257827021181583404541015625`), `setScale(2, RoundingMode.HALF_UP)` 패턴, JSON에서는 string 직렬화로 client 부동소수 손실 회피가 표준 권고. - **Smithy**: AWS의 API modeling DSL. SDK 코드 생성 친화적, 단 외부 ecosystem에서는 OpenAPI보다 미성숙. ### HTTP contract surface 표준 (conditional request / cache / pagination) versioning·schema 와 별개로, HTTP API surface 자체의 일반 계약 표준. (RFC 9110/9111 은 IETF official-standard, AIP 는 Google community guideline) - **Conditional request (RFC 9110 §13)**: `ETag` 는 representation 의 opaque validator (weak `W/"..."` 또는 strong). write 는 `If-Match` 로 optimistic concurrency 검증 — condition 이 false 면 **412 Precondition Failed**. read 는 `If-None-Match` 로 cache validation — match 면 **304 Not Modified** (body 없음, client 저장본 사용). RFC 9110 은 `If-Match` 에 *strong comparison* 을 MUST 로 요구한다. - **HTTP caching (RFC 9111 §5.2)**: `Cache-Control` directive — `no-store` (저장 금지, 인증 API 안전 default), `private` (shared cache 저장 금지), `public` (Authorization 있어도 shared cache 허용), `max-age=N` (stale 판정 초). 협상/인증 응답은 `Vary` (RFC 9110 §12.5.5) 로 어떤 request 부분이 content 선택에 영향을 줬는지 명시해 proxy/CDN cache poisoning 을 막는다. - **Pagination (Google AIP-158, JSON:API)**: offset (`page`/`size`) vs cursor (opaque token). AIP-158 은 page token 이 opaque + URL-safe MUST, server-side size cap SHOULD coerce, empty next-token = end-of-collection 을 규정. JSON:API 는 `links` object 안의 `first`/`last`/`prev`/`next` key 위치를 정의. 구체 숫자(size cap, TTL)는 표준이 아닌 구현 trade-off. - **Transport error 의미 구분 (RFC 9110 §15)**: 413 Content Too Large, 406 Not Acceptable (응답 표현 협상 실패) vs 415 Unsupported Media Type (요청 본문 format), 405 Method Not Allowed (+ `Allow` header MUST). 같은 code 로 뭉개면 표준 의미가 손실된다. - **Long-running operation (Google AIP-151 + RFC 9110)**: 비동기 처리는 **202 Accepted** + `Location` polling URL + Operation 객체(`done`/`response`/`error`). `Retry-After` 로 polling interval 권고. ## 한계 / 주의점 ### Compatibility / deprecation 측 - **Stripe freeze-forever**: 무기한 구버전 유지 비용이 외부 결제 컨슈머 규모에서만 정당화된다. internal API에 그대로 차용하면 server 코드에 N개 버전 분기를 영구 운반하게 된다. - **GitHub 24개월 EOL + `410 Gone`**: 길어 보이는 EOL window지만 catalog에 EOL 응답 코드(410)를 명시하지 않으면 client 입장에서 *어느 날 갑자기 410*과 다를 바 없다. EOL 응답 코드 자체를 contract에 박는 것이 필요하다. - **Twitter tier-based (legacy/current/beta)**: 트랙별 행위 분기가 server-side 복잡도와 운영 비용을 곱한다. 단일 팀 / internal-first 환경에 과하다. - **Spring HATEOAS (links over versions)**: 이론적으로 우아하지만 실제 client가 `_links`를 dynamic하게 따라가는 경우는 드물고, 학습 곡선과 client 구현 강제 비용이 크다. - **Google AIP-180 `enum value 제거 = breaking`**: client switch/case 누락을 유발하므로 strict 분류가 맞지만, enum value 추가 또한 client 입장에서 unknown enum 처리 정책이 없으면 깨진다 — server-side enum addition을 "additive"로만 분류하는 단순화는 위험하다. - **`Sunset` 단독 사용**: RFC 8594는 *언제 사라지는지*만 알린다. 같은 자원이 *이미 deprecated인지*는 `Deprecation` 헤더로 함께 보내야 정합이다. Sunset만 보내면 "사라질 날짜는 알지만 지금 권장 여부는 모름" 상태가 된다. - **`Sunset` 헤더 단독 사용 금지 — `Deprecation` draft와 paired**: IETF httpapi WG 권고에 따르면 `Sunset` 헤더는 `Deprecation` 헤더(draft-ietf-httpapi-deprecation-header, RFC 9745 진행)와 paired로 송신해야 client tooling이 deprecation 상태를 감지할 수 있다. paired invariant는 "Sunset 시점 ≥ Deprecation 시점". 추가로 `Link: ; rel="deprecation"` / `rel="sunset"`을 함께 보내 사람-가독 가이드를 연결한다. ca-tmpl처럼 marker만 OpenAPI에 박고 응답 헤더 paired 송신을 누락하면 외부 client interceptor가 deprecation을 자동 인지하지 못한다. ### Schema / serialization 측 - **Avro / Protobuf strict typing**: schema registry가 backward/forward 자동 검사로 강력하나, 외부 REST API가 JSON인 환경에서는 outbox / event 한정 도입이 현실적이다. - **Smithy**: AWS SDK 친화적이지만 외부 ecosystem(예: third-party tooling, doc generator) 성숙도가 OpenAPI 대비 낮다. - **Jackson default**: `FAIL_ON_UNKNOWN_PROPERTIES=true`는 strict inbound와 정합하나, `FAIL_ON_NULL_FOR_PRIMITIVES=false`는 null/empty/missing 분리 정책과 **불일치**다 — 명시적으로 override하지 않으면 contract가 깨진 줄도 모르고 0이 흘러간다. - **"Jackson은 unknown field tolerant가 default"라는 오해**: 보안/계약 측면에서 unknown inbound를 silently 허용하면 typo로 인한 데이터 손실 + payload smuggling 모두 위험. strict inbound가 안전 default. - **JSON 환경의 Protobuf `reserved` 흉내**: Protobuf는 field number / name 재사용을 wire-format 수준에서 영구 차단한다(`reserved 3, 5;` / `reserved "foo";`). OpenAPI 3.1 / JSON Schema 2020-12에는 동등 시맨틱이 없다 — `deprecated: true`는 *비권장* 신호일 뿐 재사용 차단이 아니고, field가 사라지면 schema에서도 사라져 미래 재사용 방지 불가. 현실적 대안은 두 가지: (1) **OpenAPI `x-removed-fields` 같은 Specification Extension**으로 schema SSOT에 catalog를 통합하고 자체 lint로 재사용 검출, (2) **별도 markdown catalog**(예: `docs/removed-fields-catalog.md`)에 제거된 이름/번호/일자 기록 후 CI에서 OpenAPI diff와 cross-check. 둘 다 표준 검증 도구가 없어 자체 도구 작성이 따라온다. (needs-confirmation) - **`new BigDecimal(double)` 함정**: 같은 `0.1`이 `BigDecimal.valueOf(0.1)` (정확)과 `new BigDecimal(0.1)` (부동소수 잔차)으로 갈린다. 코드 review 규칙으로 차단하지 않으면 unit test 통과 + 운영에서 1원 차이 인시던트가 흔하다. - **ISO-8601 offset 없는 datetime**: `2026-05-22T11:30:00`는 표준상 valid이지만 timezone이 누락된다. 서버 timezone에 따라 의미가 달라지므로 contract에서는 offset 필수로 강제해야 한다. 직렬화 형식을 `WRITE_DATES_AS_TIMESTAMPS=false`로만 핀해도 `JavaTimeModule`(`jackson-datatype-jsr310`)이 등록되지 않으면 `LocalDateTime`이 `[2026,5,22,...]` 배열로 직렬화되므로, module 등록 + effective 직렬화 동작 테스트가 함께 필요하다. - **naming strategy 변경 = 전역 breaking change**: snake_case ↔ camelCase 같은 `PropertyNamingStrategy` 전역 변경은 모든 응답 field 이름이 바뀌는 것과 같아 deprecation window 없이 적용하면 client 가 일제히 깨진다. naming 은 초기에 고정하고 이후 변경을 breaking change catalog 대상으로 다뤄야 한다. - **`@JsonInclude(NON_NULL)` 의 의미 손실**: null 생략은 payload 를 줄이지만 "값이 null" 과 "field 부재" 를 구분 불가하게 만든다. 부분 갱신(PATCH/merge-patch) contract 에서는 이 구분이 의미를 가지므로 3-상태(`JsonNullable`/`Optional`) 표현을 별도로 둬야 하고, 무분별한 NON_NULL 전역 적용은 이 의미 분리를 무너뜨린다. ### 흔한 오해 - "Stripe 방식이 표준이다" — IETF/W3C 표준이 아니고 진영별 사례다. 외부 결제 컨슈머 규모를 가정한 trade-off의 결과다. - "`Sunset` 헤더만 보내면 deprecation은 끝이다" — 잘못. `Deprecation` 헤더(현재 진행 중인지)와 `Sunset` 헤더(언제 사라지는지)는 함께 사용해야 정합이다. - "Jackson은 unknown field tolerant가 안전한 default다" — 잘못. inbound strict가 보안/계약 안전 default이고, outbound는 schema에 없는 field가 노출되지 않도록 controlled해야 한다(소위 **strict inbound / tolerant outbound**가 아니라 "strict inbound / schema-controlled outbound"가 정확). - "enum 값 추가는 무조건 additive다" — server-side 입장에서는 additive지만 client 입장에서는 unknown enum 처리 정책이 없으면 깨진다. client side에 unknown enum fallback이 contract로 명시되어야 비로소 additive다. ## Project Application - [[wiki/projects/ca-tmpl/api-evolution-and-schema]] — ca-tmpl 의사결정 기록 (현재 `documented-only`, Phase C2 미진입). 실제 구현 여부는 project 문서 참조. - [[raw/project-notes/ca-skeleton-operational-contract]] §13 API Contract Surface / §16 Schema / Serialization Contract / §18 API Compatibility / Deprecation / §29 G-F (외부 근거 인덱스) - [[raw/branch-notes/feature-api-compatibility-deprecation-contract]] — breaking change catalog(7행), 90d/30d migration window, OpenAPI `deprecated: true` marker, Sunset 헤더 채택 - [[raw/branch-notes/feature-schema-serialization-contract]] — ISO-8601 offset/UTC, BigDecimal scale 2 + HALF_UP, unknown field strict inbound, null/empty/missing 의미 분리 위 branch-note들이 (a) breaking change 7 분류 + migration window + deprecation marker 위치, (b) serialization producer 책임(date/time/money/enum/null/unknown)을 계약으로 둔다. canonical 승급 여부와 검증 등급은 해당 project 문서가 판정한다. ca-tmpl 의 **HTTP contract surface (versioning/pagination/conditional/cache/OpenAPI)** 는 위 두 축과 달리 실제 코드로 구현·로컬 검증됐다 — 구현 사실과 검증 등급은 [[wiki/projects/ca-tmpl/api-evolution-and-schema]] 의 "API contract baseline 구현" 절 참조. ## Claim-backed Knowledge > 각 Knowledge Point 는 이미 §Sources 에 인용된 자료로만 뒷받침된다. company-tech-blog 출처는 사례일 뿐 official best practice 로 격상하지 않는다. | Knowledge Point | Supporting Claims | Confidence | Notes | |---|---|---|---| | `Sunset` 헤더는 자원이 응답 불가가 될 시점을 HTTP-date 로 알리며 `Deprecation` 헤더와 paired 송신해야 client tooling 이 deprecation 상태를 감지 | [[raw/official-docs/compat-rfc-8594-sunset-header]], [[raw/official-docs/sunset-deprecation-headers-paired-usage]] | high | `official-standard`(RFC 8594) + IETF httpapi draft. "Sunset 단독 충분" 금지. invariant: Sunset 시점 ≥ Deprecation 시점 | | Google AIP-180 은 enum 제거/의미변경, 응답 필드 제거, 기본값 변경, required request field 추가를 breaking 으로 분류 | [[raw/official-docs/api-versioning-google-aip-180]] | high | `official-reference` (Google community guideline, IETF/W3C 표준 아님). additive 만 minor 허용 | | Jackson `FAIL_ON_UNKNOWN_PROPERTIES` default `true` (strict inbound) 이나 `FAIL_ON_NULL_FOR_PRIMITIVES` default `false` (null/missing primitive → 묵시적 0) | [[raw/official-docs/schema-jackson-unknown-field-handling]] | high | `official-vendor-doc`. "Jackson default 가 안전" 금지 — 후자는 명시 override 필요 | | `new BigDecimal(double)` 은 부동소수 잔차를 남기므로 `BigDecimal.valueOf` + `setScale(2, HALF_UP)` + JSON string 직렬화 권고 | [[raw/official-docs/schema-bigdecimal-money-serialization-java]] | high | `official-vendor-doc`. client 부동소수 손실 회피 | | ISO-8601 offset datetime 이 timezone ambiguity 회피의 정석, offset 없는 표현은 서버 timezone 의존 | [[raw/official-docs/schema-jackson-unknown-field-handling]] | medium | wire 계약에서 offset 강제 근거 (ISO-8601 일반 상식 + Jackson 직렬화 자료) | | OpenAPI 3.1 은 JSON Schema 2020-12 정합의 machine-readable HTTP API contract 이며 `deprecated: true` marker 를 schema/operation 양쪽에 둘 수 있음 | [[raw/official-docs/openapi-spec-3-1-0]] | high | `official-standard`(OAS/Linux Foundation). "marker 만으로 client 가 알아서 migrate" 금지 | | Protobuf `reserved` 는 field number/name 재사용을 wire-format 수준에서 영구 차단하나 OpenAPI/JSON Schema 에는 동등 시맨틱이 없음 | [[raw/official-docs/schema-protobuf-vs-json-evolution]], [[raw/official-docs/protobuf-reserved-vs-json-openapi-extension]] | medium | `official-reference`. "JSON 에서 완벽 흉내" 금지 — `x-` extension + 자체 lint 필요, needs-confirmation | | RFC 9110 conditional request: `ETag` validator + `If-Match`(write, strong comparison MUST)→412 + `If-None-Match`(read)→304; RFC 9111 cache directive(`no-store`/`private`/`public`/`max-age`) + `Vary` 로 cache poisoning 방지 | [[raw/official-docs/rfc9110-http-semantics]], [[raw/official-docs/rfc9111-http-caching]] | high | `official-standard`(IETF). ca-tmpl 의 weak/lenient `If-Match` 비교는 skeleton 단순화 — project 문서 참조 | | Pagination: AIP-158 은 page token opaque+URL-safe MUST, server-side size cap SHOULD coerce, empty next-token = EoC. JSON:API 는 `links` 의 first/last/prev/next 위치 정의 | [[raw/official-docs/spring-data-pageable-defaults]] (offset/zero-indexed) | medium | `official-vendor-doc`(Spring). size cap 숫자/TTL 은 표준 아닌 구현 trade-off | ## 내가 설명할 수 있어야 하는 것 - **API evolution 의 세 영역 분리**: compatibility/deprecation vs schema/serialization vs HTTP contract surface (versioning/pagination/conditional/cache). 세 영역이 framework default 가 아니라 명시 계약이어야 하는 이유. - **`Sunset` vs `Deprecation` 헤더의 역할 분리**와 paired 송신 이유, paired invariant. - **breaking change 분류 기준** (enum 축소/제거, 응답 필드 제거, 기본값 변경, required request field 추가) 과 "internal API 니까 그냥 한다" 가 위험한 이유 (client deploy lag). - **strict inbound / schema-controlled outbound** 의 정확한 의미와 Jackson 의 두 feature default 차이. - **money 직렬화**에서 `double` 위험 / `BigDecimal.valueOf` / HALF_UP / JSON string 직렬화 근거. - **conditional request** 가 DB optimistic lock 과 같은 충돌의 HTTP 표현이라는 점 (ETag → If-Match → 412, If-None-Match → 304), strong vs weak comparison 차이. - **인증 API 의 안전한 cache default = `no-store`** + `Vary` 가 cache poisoning 을 막는 원리. - **offset vs cursor pagination** trade-off, size cap 이 DoS 방어인 이유, page token opacity 의 의미. - **transport error 의미 구분** (406 vs 415, 405 + `Allow`, 413/414) 을 같은 code 로 뭉개면 안 되는 이유. ## Interview Questions - **90d public + 30d internal migration window**의 근거는? 더 짧게/길게 잡으면 어떤 비용이 생기는지? Stripe(freeze forever)나 GitHub(24mo EOL)와 비교했을 때 internal-first 환경에서 90d가 합리적인 이유는? - **`Sunset` 헤더와 `Deprecation` 헤더의 차이**는? 둘 중 하나만 보내면 client 입장에서 어떤 정보가 빠지는지? - **enum value 추가/제거가 breaking change**가 되는 이유는? client side에 unknown enum fallback이 있을 때와 없을 때 분류가 어떻게 달라지는지? - **strict inbound / tolerant outbound**가 무슨 의미인지? Jackson `FAIL_ON_UNKNOWN_PROPERTIES`와 `FAIL_ON_NULL_FOR_PRIMITIVES`는 default가 어떻게 잡혀 있고, 어느 쪽을 override해야 하는지? - **money 직렬화에서 `BigDecimal` scale 2 + HALF_UP**을 택한 이유는? `double`이 위험한 이유, `new BigDecimal(double)` 함정, JSON string 직렬화로 client 부동소수 손실을 회피하는 이유를 설명할 수 있는지? ## Do Not Overclaim - "Stripe 방식이 API versioning의 표준이다"라고 말하면 안 된다 — 진영별 사례이며 외부 결제 컨슈머 규모에 특화된 trade-off다. - "`Sunset` 헤더만 보내면 deprecation 정책으로 충분하다"라고 말하면 안 된다 — `Deprecation` 헤더와 함께 사용해야 정합이다. - "OpenAPI `deprecated: true`로 표시했으니 client가 알아서 migration한다"라고 단정하면 안 된다 — schema marker는 신호일 뿐이고 실제 cutover는 migration window + contract test + compatibility fixture가 함께 강제해야 한다. - "Jackson default가 안전하다"고 단정하면 안 된다 — `FAIL_ON_UNKNOWN_PROPERTIES`는 strict default이지만 `FAIL_ON_NULL_FOR_PRIMITIVES`는 lenient라 null/missing primitive가 묵시적으로 0이 된다. - "Avro / Protobuf로 가면 schema evolution이 자동 검사된다"라고 일반화하면 안 된다 — registry 인프라(예: Confluent Schema Registry)와 wire format 변경 비용이 따라온다. 외부 REST가 JSON인 환경에서는 outbox/event 한정 도입이 현실적이다. - "narrow enum / 응답 필드 제거 / 필드 rename"을 "internal API니까 그냥 한다"라고 정당화하면 안 된다 — client가 deploy lag을 가지면 internal에서도 breaking이다. ## Sources ### 공식 표준 / 표준 후보 - [[raw/official-docs/compat-rfc-8594-sunset-header]] — IETF RFC 8594 (HTTP `Sunset` header) - [[raw/official-docs/sunset-deprecation-headers-paired-usage]] — IETF RFC 8594 + Deprecation draft paired 사용 권고 (Sunset 단독 금지) - [[raw/official-docs/api-versioning-google-aip-180]] — Google AIP-180 (Backwards compatibility 분류) - [[raw/official-docs/schema-jackson-unknown-field-handling]] — Jackson DeserializationFeature default - [[raw/official-docs/schema-bigdecimal-money-serialization-java]] — Java BigDecimal scale/HALF_UP + JSON string 직렬화 - [[raw/official-docs/schema-avro-evolution-rules]] — Avro backward/forward/full compatibility - [[raw/official-docs/schema-protobuf-vs-json-evolution]] — Protobuf `reserved` field semantics - [[raw/official-docs/protobuf-reserved-vs-json-openapi-extension]] — Protobuf `reserved` 시맨틱의 JSON/OpenAPI 환경 흉내 대안 비교 (G-F follow-up, needs-confirmation) - [[raw/official-docs/rfc9110-http-semantics]] — IETF RFC 9110 (HTTP Semantics): conditional request(ETag/If-Match/If-None-Match/304/412), transport error(406/413/414/415/405+Allow), HEAD/OPTIONS, 202+Retry-After, Vary - [[raw/official-docs/rfc9111-http-caching]] — IETF RFC 9111 (HTTP Caching): `no-store`/`private`/`public`/`max-age` directive - [[raw/official-docs/openapi-spec-3-1-0]] — OpenAPI 3.1.0 (machine-readable HTTP API contract, JSON Schema 2020-12 정합) - [[raw/official-docs/google-aip-185-resource-versioning]] — Google AIP-185 (major-only `/v1` path versioning) - [[raw/official-docs/google-aip-158-pagination]] — Google AIP-158 (page token opacity + size cap + EoC) - [[raw/official-docs/jsonapi-pagination-format]] — JSON:API pagination link key/위치 - [[raw/official-docs/google-aip-151-long-running-operations]] — Google AIP-151 (LRO Operation shape + polling) - [[raw/official-docs/spring-data-pageable-defaults]] — Spring Data `Pageable` zero-indexed + size default + `DEFAULT_MAX_PAGE_SIZE` 2000 ### 진영별 사례 (표준 아님) - [[raw/company-tech-blogs/api-versioning-stripe-date-based]] — Stripe date-based versioning (account pin + freeze) - [[raw/company-tech-blogs/api-versioning-github-rest-date-header]] — GitHub `X-GitHub-Api-Version` + 24mo EOL + `410 Gone` ### Canonical (프로젝트 결정 사실) - [[raw/project-notes/ca-skeleton-operational-contract]] §13 / §16 / §18 API Compatibility / Deprecation / §29 G-F - [[raw/branch-notes/feature-api-compatibility-deprecation-contract]] - [[raw/branch-notes/feature-schema-serialization-contract]] ## Cluster / 묶음 - [[wiki/blog/ca-tmpl-api-evolution-and-schema-2026-07-02]]