40 KiB
title, source_type, status, branch, parent_branch, related_projects, tags, created, last_reviewed, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
| title | source_type | status | branch | parent_branch | related_projects | tags | created | last_reviewed | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-schema-serialization-contract | branch-note | verified | feature-schema-serialization-contract |
|
|
2026-05-21 | 2026-06-04 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-015 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-015 |
|
1 | 201d16bfaf7835389e3947c5e47c43435979b3a1caf75c6eb517d8b4601a0f12 |
Ground-truth 대조 (2026-06-04, ca-tmpl @5d89766 "schema/serialization 계약 직렬화 출력측 구현"): 본 branch 의 직렬화 출력측 구현 (Implementation Record Phase C2) 을 ca-tmpl commit
5d89766의 실제 코드와 1:1 대조해 확인했다 —no_bigdecimal_double_constructorArchUnit 룰(CleanArchitectureTest,callConstructor(BigDecimal.class, double.class)/float.class),BigDecimalDoubleConstructorFixture+ArchitectureViolationFixtureTest,JacksonSerializationPolicyTest(JacksonProperties바인딩 + wiredObjectMapper직렬화 동작:OffsetDateTime→"1985-04-12T23:20:50.52Z",LocalDate→"2026-06-02",new BigDecimal("1.10")→1.10, 대형 값 비-scientific),application.yml/application-test.yml/.env의spring.jackson.serialization.write-dates-as-timestamps=false+spring.jackson.generator.write-bigdecimal-as-plain=true핀 모두 존재 확인.locally-verified. wiki/projects/ca-tmpl/api-evolution-and-schema.md 에 reconcile 완료. D5(OpenAPI drift gate)/D6(제거-field 재사용 도구)/D7(Avro)/per-API money string-vs-number 코드 시연은 미구현(documented-only/planned/needs-confirmation) 으로 보존.
branch: feature-schema-serialization-contract
Layer:
raw/branch-notes/— JSON schema와 serialization 기준을 정의합니다.
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: JSON·date·decimal serialization contract test가 통과한다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-JSON-001@1 |
JSON stack은 Spring Boot transitive Jackson 2.18.x다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
목표
날짜, 시간대, enum, 금액, null, unknown field 정책이 암묵적이면 API contract가 쉽게 깨집니다. skeleton은 serialization 기준과 schema drift 검증 기준을 가져야 합니다.
- 이슈:
- PR:
범위
포함 범위
- date/time/timezone serialization 기준.
- BigDecimal/money scale/rounding 기준.
- enum unknown value 처리 기준.
- null/empty/missing field 의미 구분.
- unknown JSON field 허용/거부 기준.
- response field rename/versioning 기준.
- OpenAPI schema drift 검증.
제외 범위
- domain-specific schema.
- multi-language SDK generation.
- public API deprecation policy.
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/schema-jackson-unknown-field-handling | Jackson DeserializationFeature default (FAIL_ON_UNKNOWN_PROPERTIES=true가 ca-tmpl strict inbound와 정합 |
| raw/official-docs/schema-bigdecimal-money-serialization-java | Java BigDecimal scale/HALF_UP 표준 + JSON string 직렬화 권장 |
| raw/official-docs/schema-protobuf-vs-json-evolution | wire-format + reserved field가 ca-tmpl JSON 환경에서 가장 크게 보강할 부분 |
| raw/official-docs/schema-avro-evolution-rules | backward/forward/full compat 자동 검사; outbox/event 한정 도입 가치 |
| raw/official-docs/protobuf-reserved-vs-json-openapi-extension | 참조 |
| raw/official-docs/rfc3339-datetime-utc | IETF RFC 3339 (Standards Track) — datetime UTC + "Z" suffix + ISO 8601 profile 표준 (D2 datetime/UTC 정책의 normative 근거) |
| raw/official-docs/iana-media-types-registry | IANA Media Types Registry — application/json / application/problem+json 등 response Content-Type 표준 어휘의 1차 authoritative 출처 (본 branch 결정 범위 밖 — 참조용, Decision Evidence Map 미연결) |
외부 근거 / 대안 조사 (2026-05-22 — Group G-F: Schema / Serialization)
본 branch의 ISO-8601 offset/UTC + BigDecimal scale 2 HALF_UP + unknown field strict inbound·tolerant outbound + null/empty/missing 의미 분리 결정에 대한 외부 source.
- 채택 결정 (Jackson + ISO-8601 + BigDecimal HALF_UP):
- raw/official-docs/schema-jackson-unknown-field-handling — Jackson DeserializationFeature default (
FAIL_ON_UNKNOWN_PROPERTIES=true가 ca-tmpl strict inbound와 정합) - raw/official-docs/schema-bigdecimal-money-serialization-java — Java BigDecimal scale/HALF_UP 표준 + JSON string 직렬화 권장
- raw/official-docs/schema-jackson-unknown-field-handling — Jackson DeserializationFeature default (
- 검토한 대안:
- 대안 1: Jackson default lenient —
FAIL_ON_NULL_FOR_PRIMITIVES=falsedefault가 ca-tmpl null/empty/missing 분리와 불일치 → 명시 override 필요 - 대안 2: Protobuf strict typing — raw/official-docs/schema-protobuf-vs-json-evolution (wire-format +
reservedfield가 ca-tmpl JSON 환경에서 가장 크게 보강할 부분) - 대안 3: Avro schema evolution — raw/official-docs/schema-avro-evolution-rules (4가지 schema resolution 규칙 확인(SAER-C1~C4); backward/forward/full compatibility level enforcement 정의는 Avro spec 본문 미확보 — Confluent Schema Registry docs 별도 fetch 필요. outbox/event 한정 도입 가치)
- 대안 4: JSON Schema validation — REST 외부 인터페이스에서 추가 검증
- 대안 5: Smithy / OpenAPI 3.1 — API modeling DSL, 별도 도구 도입
- 대안 1: Jackson default lenient —
- 비교 핵심: Jackson default는 ca-tmpl strict inbound 정책과 일치하나 null primitive 처리는 명시 override 필요. Protobuf
reserved(field number/name 재사용 차단)가 JSON 환경에서 ca-tmpl이 보강할 부분 — OpenAPI extension으로 흉내 가능. Avro는 4가지 schema resolution 규칙(SAER-C1~C4 확인)을 정의하나 backward/forward/full compatibility level의 자동 enforcement 정의는 미확보(Confluent Schema Registry 별도 확인 필요), 외부 REST는 JSON 유지, outbox/event 한정 도입 권장. BigDecimal은new BigDecimal(double)함정 + HALF_UP 표준 정의 + JSON string 직렬화가 client 정밀도 손실 회피책.
후속 보강 (2026-05-22): Protobuf reserved 시맨틱의 JSON 환경 흉내 정책 미정 — OpenAPI x-removed-fields extension 또는 자체 catalog 채택 검토 필요. raw/official-docs/protobuf-reserved-vs-json-openapi-extension 참조.
TODO
TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Decisionized Work Items" 참조. datetime/money/enum unknown/null-empty-missing/unknown field/OpenAPI drift 모두 표 row로 반영됨. response field rename은
feature-api-compatibility-deprecation-contract로 위임. 잔존 TODO 없음.
Work Item Contract
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.
| field | required | rule |
|---|---|---|
| Decision | yes | 구현자가 선택해야 하는 기본값 |
| Allowed | yes | 허용되는 예외와 조건 |
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
| Canonical extraction target | yes | wiki/projects 승급 위치 |
진행 중 메모
- schema contract는 response mapper와 API contract branch에 연결됩니다.
결정 사항
- 2026-05-21: serialization을 framework default에 암묵적으로 맡기지 않음.
- 2026-05-22: datetime은 ISO-8601 offset datetime을 기본으로 하고 서버 timezone은 UTC.
- 2026-05-22: money/decimal은 string serialization 또는 fixed scale decimal 중 API별 한 가지를 명시. 기본 scale은 2, rounding은
HALF_UPunless domain overrides. - 2026-05-22: unknown JSON field는 request에서 fail-fast, response에서는 schema에 없는 public field 노출 금지.
- 2026-05-22: OpenAPI drift 집행권은 verification suite가 소유하고 이 branch는 serialization producer.
- 2026-05-22: 제거된 field name과 number(있다면)의 재사용 금지 정책을 OpenAPI
x-removed-fieldsextension 또는 markdown 카탈로그로 정의. 코드 단계에서 도구 선택. (status: needs-confirmation)
Decisionized Work Items
| item | Decision | Allowed | Forbidden | Required test | Failure condition |
|---|---|---|---|---|---|
| date/time | ISO-8601 offset datetime, UTC default | date-only for calendar fields | timezone-less datetime | serialization snapshot | timezone 없는 datetime |
| money/decimal | fixed scale 2 + HALF_UP default |
domain-specific scale with schema note | binary floating point for money | JSON schema test | scale/rounding unspecified |
| enum unknown | request unknown enum -> validation failure | compatibility adapter can map legacy value | fallback to arbitrary enum | enum failure test | unknown enum silently accepted |
| null/empty/missing | mapper owns semantic distinction | optional field documented nullable | framework default ambiguity | mapper/schema test | null/empty/missing mixed |
| unknown field | request fail-fast, response forbidden | compatibility mode with explicit env | schema-less payload | OpenAPI drift | schema 없는 field exposed |
결정-근거 매핑
본 branch 의 결정을 raw source Claim ID 로 매핑. Jackson default / BigDecimal 표준 / Avro·Protobuf 비교 대안에 대해 직접 supporting 근거가 있음.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | serialization 을 framework default 에 암묵적으로 맡기지 않음 | raw/official-docs/schema-jackson-unknown-field-handling.md#SJUF-C2 (FAIL_ON_NULL_FOR_PRIMITIVES default disabled — null → 0 silent 변환) |
official-vendor-doc |
Spring Boot JacksonProperties 가 일부 default override 가능 — spring.jackson.deserialization.* 명시 권장이 본 branch 외 별도 검증 필요 |
| D2 | datetime = ISO-8601 offset datetime, 서버 timezone = UTC | raw/official-docs/rfc3339-datetime-utc.md#RFC3339-C2 (true interoperability = UTC, daylight saving 회피), #RFC3339-C4 (RFC 3339 = ISO 8601 profile, 새 Internet protocol 에서 SHOULD), #RFC3339-C1 ("Z" suffix = UTC offset 00:00), #RFC3339-C5 (ABNF: time-offset = "Z" / time-numoffset — alphabetic timezone 약어 금지), #RFC3339-C7 (생성 시 대문자 "Z" SHOULD), #RFC3339-C8 (예시: 1985-04-12T23:20:50.52Z) |
official-standard (IETF RFC 3339) |
RFC 3339 는 numeric offset (예: -08:00) 도 valid syntactically (RFC3339-C2 Does-not-prove) — "UTC 만 허용" 의 strict MUST 는 아니므로 ca-tmpl "서버 timezone = UTC" 강제는 운영 정책 보강. Jackson WRITE_DATES_AS_TIMESTAMPS=false + JavaTimeModule 의 실제 직렬화 형식은 별도 검증 필요 (Usage Boundary 참조) |
| D3 | money/decimal = string serialization 또는 fixed scale decimal 중 API별 한 가지 명시. 기본 scale = 2, rounding = HALF_UP (domain override 허용) |
raw/official-docs/schema-bigdecimal-money-serialization-java.md#SBMS-C1 (scale 정의), raw/official-docs/schema-bigdecimal-money-serialization-java.md#SBMS-C2 (HALF_UP 정의), raw/official-docs/schema-bigdecimal-money-serialization-java.md#SBMS-C3 (new BigDecimal(double) 위험), raw/official-docs/schema-bigdecimal-money-serialization-java.md#SBMS-C4 (new BigDecimal(String) 권장) |
official-vendor-doc (Oracle Javadoc) |
SBMS-C2 "Does not prove" 컬럼: HALF_UP 이 회계 / 세무 표준이 모든 도메인에 강제되지는 않음 — domain override 정책 정합. KRW/JPY 같은 scale 0 통화는 별도 처리 필요 |
| D4 | request unknown JSON field = fail-fast, response = schema 에 없는 public field 노출 금지 | raw/official-docs/schema-jackson-unknown-field-handling.md#SJUF-C1 (Jackson 2.13 default = FAIL_ON_UNKNOWN_PROPERTIES=true enabled), raw/official-docs/schema-jackson-unknown-field-handling.md#SJUF-C4 (READ_UNKNOWN_ENUM_VALUES_AS_NULL default disabled — 정합), raw/official-docs/schema-avro-evolution-rules.md#SAER-C2 (Avro 는 반대로 unknown 자동 ignore — 대조 근거) |
official-vendor-doc (Jackson) + official-standard (Avro 대조) |
response 측 "schema 없는 field 노출 금지" 는 Jackson 만으로 자동 강제 불가 — OpenAPI drift 검증 필요 (SJUF-C1 Does-not-prove). Avro / Protobuf 모델과 ca-tmpl 결정이 반대 방향임을 보존 |
| D5 | OpenAPI drift 집행권 = verification suite. 본 branch 는 serialization producer | (sibling branch feature-api-contract-baseline 와 책임 분담; 외부 raw 직접 근거 없음. 해당 sibling branch 의 OpenAPI drift gate Decision 에 의존 — §엣지·실패·의존 참조) |
UNSUPPORTED_DECISION | sibling branch governance. sibling branch 미작성/미착수 시 D4 의 response-side "schema 없는 field 미노출" 강제는 본 branch 완료 후에도 미보증 상태 — sibling status + 대응 Decision ID 확인 필요 |
| D6 | 제거된 field name / number 재사용 금지 정책을 OpenAPI x-removed-fields extension 또는 markdown 카탈로그로 정의 (status: needs-confirmation) |
raw/official-docs/schema-protobuf-vs-json-evolution.md#SPVJ-C2 (Protobuf field number 재사용 금지 표준), raw/official-docs/schema-protobuf-vs-json-evolution.md#SPVJ-C3 (reserved 필수), raw/official-docs/schema-protobuf-vs-json-evolution.md#SPVJ-C4 (재사용 시 risk catalog: 디버깅 손실 / parse error / PII 누출 / 데이터 손상), raw/official-docs/schema-protobuf-vs-json-evolution.md#SPVJ-C5 (JSON encoding 에서 field name 재사용 위험), raw/official-docs/protobuf-reserved-vs-json-openapi-extension.md#PRVJ-C1, raw/official-docs/protobuf-reserved-vs-json-openapi-extension.md#PRVJ-C2, raw/official-docs/protobuf-reserved-vs-json-openapi-extension.md#PRVJ-C5 (OpenAPI x- extension 메커니즘 — 정책 자체는 자체 lint 필요), raw/official-docs/protobuf-reserved-vs-json-openapi-extension.md#PRVJ-C6 (JSON Schema deprecated 가 재사용 차단 아님) |
official-standard (Protobuf + OpenAPI + JSON Schema) |
PRVJ-C5 Does-not-prove: x-removed-fields 같은 특정 확장이 표준이 아님 — 자체 lint 작성 필요. 도구 선택 자체는 코드 단계 (status needs-confirmation) |
| D7 (대안 비교) | Avro Schema Registry 채택은 outbox/event 한정 검토 가치 — REST/JSON 외부 API 는 JSON 유지 | raw/official-docs/schema-avro-evolution-rules.md#SAER-C1, raw/official-docs/schema-avro-evolution-rules.md#SAER-C2, raw/official-docs/schema-avro-evolution-rules.md#SAER-C3, raw/official-docs/schema-avro-evolution-rules.md#SAER-C4 (Avro 의 4 schema resolution 규칙) |
official-standard |
Avro backward/forward/full compatibility level enforcement 정의 인용 미확보 (raw 의 "미확인 / 후속 확인 필요" 섹션 명시) — 본문 §외부 근거의 "자동 검사" 표현은 4 resolution 규칙 확인분만 근거, compatibility level 자동 enforcement 는 Confluent Schema Registry 별도 fetch 필요 |
구현 가이드
결정 (D1~D7) 이 "무엇 을 할 것인가" 라면, 본 §는 "어디에 어떻게 구현될 것인가" 의 사전 명세. sub-section 은 본 branch 의 결정·근거에서 도출되는 in-scope 만 작성. 3-rule meta principle (R1 Reference / R2 UNSUPPORTED_IMPL_DECISION / R3 OUT_OF_BRANCH_SCOPE) 준수.
1. ObjectMapper 빈의 명시 설정 (Jackson deserialization/serialization defaults)
Trace:
FAIL_ON_UNKNOWN_PROPERTIES=true명시 → D4 +SJUF-C1(Jackson 2.13 default enabled — 명시로 Spring Boot override 차단)
FAIL_ON_NULL_FOR_PRIMITIVES=true명시 → D1 +SJUF-C2(default disabled → null → 0 silent 변환 차단)
READ_UNKNOWN_ENUM_VALUES_AS_NULL=false유지 → D4 (enum) +SJUF-C4(default disabled — unknown enum 을 null 로 흡수하지 않음)
WRITE_DATES_AS_TIMESTAMPS=false+JavaTimeModule등록 → D2 +RFC3339-C4/C7(ISO-8601 offset 문자열 직렬화)
WRITE_BIGDECIMAL_AS_PLAIN=true→ D3 +SBMS-C4(지수 표기 회피)UNSUPPORTED_IMPL_DECISION: ①위 설정을
application.yml의spring.jackson.*property 로 둘지Jackson2ObjectMapperBuilderCustomizer빈으로 둘지의 wiring 위치 선택 — 근거 raw 는 property 의미만 권고하고 적용 메커니즘은 권고하지 않음. trade-off: property = 선언적·테스트 용이 / customizer =@JsonComponent등 복합 설정과 일관. → property 기본 + 복합 설정 시 customizer 보강 으로 사용자 임의 채택. ②READ_UNKNOWN_ENUM_VALUES_AS_NULL은spring.jackson.deserialization.*에 해당 key 가 없으면 Jackson default(disabled)를 그대로 따름 — Spring Boot override 부재를 ApplicationContext bean test 로 확인 필요(Claims To Verify 참조).
| 설정 | 값 | property key | Trace |
|---|---|---|---|
| unknown field | fail | spring.jackson.deserialization.fail-on-unknown-properties=true |
D4 / SJUF-C1 |
| null → primitive | fail | spring.jackson.deserialization.fail-on-null-for-primitives=true |
D1 / SJUF-C2 |
| unknown enum | not-null | READ_UNKNOWN_ENUM_VALUES_AS_NULL=false (default 유지 — bean test 로 확인) |
D4 / SJUF-C4 |
| datetime 직렬화 | ISO-8601 | WRITE_DATES_AS_TIMESTAMPS=false + JavaTimeModule |
D2 / RFC3339-C4,C7 |
| BigDecimal 직렬화 | plain | WRITE_BIGDECIMAL_AS_PLAIN=true |
D3 / SBMS-C4 |
2. BigDecimal 직렬화 형식 메커니즘
Trace: scale 2 + HALF_UP default → D3 +
SBMS-C1(scale),SBMS-C2(HALF_UP).new BigDecimal(String)경유 생성 → D3 +SBMS-C4. domain-specific scale (KRW/JPY scale 0) 허용은 D3 Open Risk 의 domain override 정책.
- UNSUPPORTED_IMPL_DECISION: JSON 직렬화를 ①
@JsonSerialize(using=ToStringSerializer.class)(string) vs ②number +WRITE_BIGDECIMAL_AS_PLAIN=true중 택1 — 근거 raw 는 string 직렬화를 권장(SBMS-C4)하나 number+plain 도 정밀도 보존 가능. trade-off: string = client 강제 파싱(정밀도 안전) / number = JSNumber정밀도 손실 위험.- 기본 선택 기준 (사용자 임의 trade-off): 외부 노출 / 금융 / public API = string (client 정밀도 안전 우선), 내부 서비스 간 API = number + plain (파싱 비용 절감). API 별 한 가지를 OpenAPI 에 명시 의무 (Decisionized Work Items
money/decimalrow 의 "fixed scale 2 + HALF_UP default" 와 정합) — 기본값에 의존하지 않고 endpoint 설계 시점에 명시.
3. 정적 강제 카탈로그 (ArchUnit / 정적 분석)
Trace:
new BigDecimal(double)/new BigDecimal(float)호출 차단 → D3 +SBMS-C3(double 생성자 정밀도 함정)
@JsonIgnoreProperties(ignoreUnknown=true)사용 차단 → D4 (annotation 우회 시 fail-fast 정책 무력화)UNSUPPORTED_IMPL_DECISION: ①rule 이름 (
no_bigdecimal_double_constructor,no_jackson_ignore_unknown_properties등 임의 명명). ②차단 레벨 (constructor-call-level vs import-level) — 근거 없는 사용자 선택. trade-off: false positive 회피 vs 회귀 차단 범위.
4. null·empty·missing mapper 책임
Trace:
request unknown enum → validation failure, legacy 값은 explicit adapter 경유 → D4 (enum) +
SJUF-C4+ Decisionized Work Itemsenum unknownrownull / empty / missing 의미 분리를 mapper 가 소유 → D1 +
SJUF-C2(Jackson default 가 분리 안 함) + Decisionized Work Itemsnull/empty/missingrow레이어 경계: null/empty/missing 분리 책임은 역직렬화 직후 ~ 유효성 검증 이전의 web inbound mapper 계층 이 소유 (Controller DTO → Command/Query 변환 시점). Domain Service 는 이미 분리된 3-상태를 받음 — Domain 에서 재분리하지 않음.
UNSUPPORTED_IMPL_DECISION: ①legacy enum 매핑 어댑터 클래스 명명 (
LegacyEnumMapper등). ②null/empty/missing 3-상태 표현 wrapper 선택 (JsonNullable<T>vsOptional<T>) — 근거 raw 가 상태 분리 필요 만 권고하고 표현 타입 은 권고하지 않음. trade-off:JsonNullable= JSON Merge Patch 의미 정합 /Optional= 표준 라이브러리·필드 직렬화 제약.
R3. OUT_OF_BRANCH_SCOPE (본 branch 결정 범위 밖 — §구현 가이드에 detail 미작성, 이관 history 만 보존):
- OpenAPI drift 집행 메커니즘 (D5): 집행권은 verification suite 소유. 본 branch 는 serialization producer 일 뿐 — drift gate 의 CI 구현 detail 은 sibling
feature-api-contract-baseline로 이관.- response field rename / versioning (TODO drain 시 위임):
feature-api-compatibility-deprecation-contract소관.- 제거 field 재사용 차단 도구 선택 (D6,
needs-confirmation):x-removed-fieldsextension vs markdown catalog 의 택1 은 코드 단계 미결정 — 본 branch 는 정책 존재 만 정의.- Avro Schema Registry 채택 (D7): outbox/event 한정 별도 검토. 외부 REST/JSON 은 JSON 유지. Confluent compatibility enforcement 메커니즘 미확보.
엣지·실패·의존
R4(깊이 게이트) 캡처용. 정상 경로 외 구현 중 부딪힐 실패/엣지/계약 의존.
- 실패·엣지 경로:
- scale 0 통화 (KRW/JPY): default scale 2 와 충돌 — domain override 로 scale 0 명시 (D3 Open Risk). schema note 없이 섞이면
money/decimal테스트 실패. - numeric offset (
-08:00): RFC 3339 상 syntactically valid (RFC3339-C2Does-not-prove) 이나 ca-tmpl 운영 정책은 "서버 timezone = UTC" 강제 — offset 비-Z출력 발생 시 운영 정책 위반으로 판정 필요. - date-only calendar field: offset datetime 강제에서 제외 (Decisionized Work Items
date/timerow 의 allowed).LocalDatevsOffsetDateTime혼용 시 snapshot 테스트로 차단. JavaTimeModule미등록:jackson-datatype-jsr310의존성 누락 또는 module 등록 누락 시LocalDateTime이[2026, 5, 21, 10, 30]배열로 직렬화되어 RFC 3339 계약 위반이 런타임에서야 발견됨 —WRITE_DATES_AS_TIMESTAMPS=false단독으로는 불충분,ObjectMapper.registerModule(new JavaTimeModule())(또는 Spring Boot auto-config 의존성) 까지 필요.- compatibility adapter 의 enum 우회: adapter 구현이 validation failure 정책을 우회할 위험 — legacy 입력은 explicit mapper 경유 강제 (Claims To Verify 참조).
- scale 0 통화 (KRW/JPY): default scale 2 와 충돌 — domain override 로 scale 0 명시 (D3 Open Risk). schema note 없이 섞이면
- 다른 계약 의존:
- raw/branch-notes/feature-api-contract-baseline 의 OpenAPI drift gate 에 의존 — D5 가 drift 집행권을 위임. 그 gate 의 schema-없는-field 차단이 본 branch 의 "response 측 미노출" 결정을 실제로 강제. 해당 sibling branch 의 status + 대응 Decision ID 확인 필요 — 미착수 시 D4 response-side 강제는 미보증.
- raw/branch-notes/feature-api-compatibility-deprecation-contract — response field rename/versioning + 제거 field 재사용 정책(D6) 의 실제 도구가 여기서 확정되면 본 branch 의
needs-confirmation해소.
검증해야 할 주장
외부 표준 (Jackson / BigDecimal / Avro / Protobuf) 는 정책 근거지만 ca-tmpl 의 ObjectMapper 빈 설정 / OpenAPI drift / 도구 선택의 실제 동작을 보장하지 않음.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| ca-tmpl 의 모든 응답에서 UTC가 아닌 datetime 또는 timezone 없는 datetime 이 발생하지 않는다 | serialization snapshot test 없으면 LocalDateTime / LocalDate 혼용 또는 non-UTC offset 허용 가능 | OpenAPI snapshot test + Jackson serialization test 로 모든 datetime 필드가 RFC 3339 UTC Z 형식인지 검증 |
planned |
ca-tmpl 의 ObjectMapper 빈이 FAIL_ON_UNKNOWN_PROPERTIES=true + FAIL_ON_NULL_FOR_PRIMITIVES=true + READ_UNKNOWN_ENUM_VALUES_AS_NULL=false 로 설정된다 |
SJUF-C1/C2/C4 default 자체는 보장되나 Spring Boot JacksonProperties 가 일부 override 가능 |
spring.jackson.deserialization.fail-on-unknown-properties=true + fail-on-null-for-primitives=true 명시 + ApplicationContext bean test (3 feature 의 effective 값 assert) |
planned |
@JsonIgnoreProperties(ignoreUnknown=true) 가 ca-tmpl 의 어떤 DTO 클래스에도 붙어 있지 않다 |
annotation 우회 시 D4 정책 무력화 | ArchUnit rule: @JsonIgnoreProperties(ignoreUnknown=true) 사용 시 build fail |
planned |
new BigDecimal(double) / new BigDecimal(float) 호출이 ca-tmpl 코드에 없다 |
SBMS-C3 위험이 실제로 발생 가능 — 컴파일러는 차단 안 함 |
ArchUnit rule + 정적 분석으로 해당 생성자 호출 차단 | planned |
| BigDecimal JSON 직렬화가 number vs string 중 명시 정책으로 일관 | SBMS-C4 권장 외에 Jackson WRITE_BIGDECIMAL_AS_PLAIN default 가 코드에 명시되지 않으면 지수 표기 가능 |
WRITE_BIGDECIMAL_AS_PLAIN=true 또는 @JsonSerialize(using=ToStringSerializer.class) 정책 채택 후 serialization snapshot test |
planned |
| OpenAPI drift 검증이 ca-tmpl response 의 모든 schema 없는 field 노출을 차단한다 | Jackson 만으로는 보장 불가 (SJUF-C1 Does-not-prove 컬럼) — verification suite 별도 책임 (D5) |
feature-api-contract-baseline + OpenAPI snapshot diff CI gate 실행 + dummy field 추가 시 fail 시연 |
planned |
제거된 field name / number 재사용 차단 도구가 ca-tmpl 에 도입된다 (status needs-confirmation) |
D6 의 x-removed-fields extension vs markdown catalog 선택이 미정 — PRVJ-C5 가 표준 부재 명시 |
(1) OpenAPI x-removed-fields extension 정의 + 자체 lint rule, 또는 (2) markdown catalog 작성 + CI grep. 둘 중 1개 채택 후 시연 |
needs-confirmation |
| Avro 채택 시 outbox/event 영역에서 backward / forward / full compatibility 가 자동 검사된다 | Avro spec page 에서 compatibility level enforcement 정의 인용 미확보 (raw "미확인 / 후속 확인 필요" 섹션) | Confluent Schema Registry docs 추가 fetch → compatibility level enforcement 메커니즘 확정 + CI step 시연 | needs-confirmation |
| ca-tmpl 의 enum unknown 정책 (validation failure) 이 compatibility adapter 가 legacy 매핑할 때 우회 가능하다 | SJUF-C4 default 와 일치하나 compatibility adapter 자체 구현이 정책 우회 위험 |
adapter 별 contract test + legacy enum 입력 시 explicit LegacyEnumMapper 경유 검증 |
planned |
| null / empty / missing 의미 분리가 모든 mapper layer 에서 일관 유지된다 | SJUF-C2 Jackson default 가 분리 안 함 — mapper code 누락 시 silent drift |
mapper별 contract test (3가지 case input → 3가지 다른 output) | planned |
테스트 계약
- timezone 없는 datetime 응답이 발생하면 실패.
- unknown enum value 처리 기준이 없으면 실패.
- schema에 없는 response field가 노출되면 실패.
- null/empty/missing이 mapper 정책 없이 섞이면 실패.
구현 기록
본 branch 의 결정 D1~D7 중 직렬화 출력측 을 ca-tmpl 코드에 반영. 입력측(D1 deser / D4 enum)과 null·empty·missing 3-상태(
Patch<T>)는 siblingfeature-boundary-validation-mapping-contract가 이미 구현 — 본 라운드는 출력측 핀 + 정적 차단 + 직렬화 동작 테스트 + 계약 문서화 만 추가. 사용자 승인 scope: ①money 는 설정+ArchUnit+문서만(sample 도메인 무변경), ②ArchUnit 은 신규no_bigdecimal_double_constructor만(@JsonIgnoreProperties 기존 룰 유지), ③D6 은needs-confirmation유지(범위 밖).
사전 현황 (이미 구현됨 — 본 branch 가 건드리지 않음)
| 항목 | 구현 위치 | 출처 branch |
|---|---|---|
deser FAIL_ON_UNKNOWN_PROPERTIES/FAIL_ON_NULL_FOR_PRIMITIVES/FAIL_ON_IGNORED_PROPERTIES/READ_UNKNOWN_ENUM_VALUES_AS_NULL=false |
application.yml spring.jackson.deserialization.* + JacksonDeserializationPolicyTest |
boundary-validation-mapping (B1) |
@JsonIgnoreProperties(ignoreUnknown=true) 차단 (web dto 한정) |
ArchUnit request_dtos_do_not_silence_unknown_fields |
boundary-validation-mapping (B1) |
| null/empty/missing 3-상태 | shared/request/Patch<T> + adapter/web/config/JacksonNullableConfig (JsonNullable) |
boundary-validation-mapping (B2) |
이번 라운드 변경 파일
src/app-bootstrap/.../architecture/CleanArchitectureTest.java— ArchUnit 룰no_bigdecimal_double_constructor추가 (new BigDecimal(double/float)생성자 차단, D3/SBMS-C3).import java.math.BigDecimal추가.src/app-bootstrap/.../architecture/violations/serialization/BigDecimalDoubleConstructorFixture.java(신규) — 위반 fixture (new BigDecimal(1.1d)/new BigDecimal(1.1f)).src/app-bootstrap/.../architecture/ArchitectureViolationFixtureTest.java— fixture 테스트no_bigdecimal_double_constructor_catches_double_and_float_constructors()추가 (vacuous pass 방지).src/app-bootstrap/.../settings/JacksonSerializationPolicyTest.java(신규) — ①JacksonProperties바인딩 assert(WRITE_DATES_AS_TIMESTAMPS=false,WRITE_BIGDECIMAL_AS_PLAIN=true), ② wiredObjectMapper동작 assert(OffsetDateTime→"1985-04-12T23:20:50.52Z",LocalDate→"2026-06-02",new BigDecimal("1.10")→1.10, 대형 값 비-scientific).src/.env—Jackson (serialization policy)블록 +SPRING_JACKSON_SER_WRITE_DATES_AS_TIMESTAMPS=false/SPRING_JACKSON_GEN_WRITE_BIGDECIMAL_AS_PLAIN=true.src/app-bootstrap/src/main/resources/application.yml—spring.jackson.serialization.write-dates-as-timestamps+spring.jackson.generator.write-bigdecimal-as-plain(env 바인딩).src/app-bootstrap/src/test/resources/application-test.yml— 위 두 키 리터럴(false/true).src/adapter-web/CLAUDE.md—## Schema / serialization contract섹션(S1 datetime/D2, S2 money/D3, S3 BigDecimal double 생성자 금지, S4 enum·null/empty/missing cross-ref, S5 out-of-scope) 추가.docs/superpowers/plans/2026-06-02-schema-serialization-contract.md(신규) — 실행 계획.
구현 결정 메모
- wiring 위치: §1① UNSUPPORTED_IMPL_DECISION(property vs customizer)는 sibling deser 측 precedent(
.env→application.ymlspring.jackson.*)를 그대로 따라 property + env 키 채택. 복합 직렬화기가 필요해지면 그때Jackson2ObjectMapperBuilderCustomizer보강. WRITE_BIGDECIMAL_AS_PLAINproperty key: Spring Bootspring.jackson.generator.*→JsonGenerator.Feature바인딩.JacksonProperties.getGenerator()로 effective 확인.- JavaTimeModule: 별도 명시 등록 안 함 — Spring Boot
starter-jsonauto-config 가 classpath 의jackson-datatype-jsr310을 자동 등록. 누락 회귀는JacksonSerializationPolicyTest의OffsetDateTime직렬화 assert 가 잡음(누락 시[1985,4,12,...]배열로 직렬화되어 실패). - registry:
SPRING_JACKSON_SER_*/GEN_*키는docs/registries/env-keys.yaml에 미등록 — 기존SPRING_JACKSON_DESER_*키도 미등록된 precedent + 해당 registry 가 curated subset(SPRING-native 는SPRING_PROFILES_ACTIVE/SERVER_PORT만 등재)인 점을 따름..env주석으로 문서화. (Work Item Contract: registry update 는 conditional) - money string-vs-number: §2 UNSUPPORTED_IMPL_DECISION 그대로 — endpoint 설계 시점 명시 의무로
adapter-web/CLAUDE.mdS2 에 문서화. sample(WorkLog)에 money 필드 없어 코드 시연 생략(사용자 승인). - enum unknown 사후 검증: §1② default 유지(
READ_UNKNOWN_ENUM_VALUES_AS_NULL=false)는 deser 측에서 이미 yml +JacksonDeserializationPolicyTest로 확인됨 — 본 branch 미변경.
검증 (locally-verified)
cd src && ./gradlew verifyCleanArchitectureDependencies→ BUILD SUCCESSFULcd src && ./gradlew :app-bootstrap:test→ BUILD SUCCESSFUL (신규JacksonSerializationPolicyTest2건 + fixture 테스트 1건 포함)cd src && ./gradlew test→ BUILD SUCCESSFUL (전체 모듈)
Claims To Verify 상태 변화
| Claim | 이전 | 이후 |
|---|---|---|
| 모든 응답에서 timezone 없는 datetime 미발생 | planned |
부분 locally-verified — WRITE_DATES_AS_TIMESTAMPS=false 핀 + OffsetDateTime/LocalDate 직렬화 동작 테스트. 단 "모든 DTO" 전수 보장은 OpenAPI snapshot(D5, sibling) 필요 → 여전히 미보증. |
| ObjectMapper effective deser 3-switch | planned |
(sibling 에서 locally-verified — 본 branch 무관) |
@JsonIgnoreProperties(ignoreUnknown=true) 부재 |
planned |
(sibling B1 ArchUnit 으로 locally-verified — web dto 한정) |
new BigDecimal(double/float) 코드 부재 |
planned |
locally-verified — no_bigdecimal_double_constructor + fixture 테스트. |
| BigDecimal 직렬화 number/string 명시 정책 | planned |
부분 — WRITE_BIGDECIMAL_AS_PLAIN=true 핀 + plain 직렬화 테스트. per-API string-vs-number 는 문서 의무(코드 강제 아님). |
| OpenAPI drift 가 schema-없는 field 차단 | planned |
미변경 — D5, sibling(api-contract-baseline 의 springdoc producer 는 존재, release-blocking drift gate 는 verification-test-suite planned). |
| 제거 field 재사용 차단 도구 | needs-confirmation |
미변경 — D6, 범위 밖 유지. |
| Avro outbox/event compat 자동검사 | needs-confirmation |
미변경 — D7, 범위 밖. |
| enum unknown adapter 우회 가능성 | planned |
미변경 — adapter 별 contract test 는 sample/도메인 구현 시점. |
| null/empty/missing mapper 일관성 | planned |
(sibling B2 Patch<T> 로 locally-verified — 본 branch 무관) |
마주친 문제
- 구현 중 빌드/테스트 실패 없음.
OffsetDateTime/BigDecimal직렬화 동작은 Spring Boot 기본값이 이미 contract 와 일치(WRITE_DATES_AS_TIMESTAMPSdefault false + JavaTimeModule auto-register)하여, 동작 테스트는 첫 실행부터 green — 본 branch 의 가치는 "기본값 일치"가 아니라 명시 핀으로 future default flip 회귀 차단 + 정적 BigDecimal 차단에 있음(D1/D2 의도와 정합).
묶음 (이 branch에서 파생된 자료)
- raw/official-docs/ci-openapi-snapshot-diff-tooling
- raw/official-docs/iana-media-types-registry
- raw/official-docs/protobuf-reserved-vs-json-openapi-extension
- raw/official-docs/rfc3339-datetime-utc
- raw/official-docs/schema-avro-evolution-rules
- raw/official-docs/schema-bigdecimal-money-serialization-java
- raw/official-docs/schema-jackson-polymorphic-deserialization
- raw/official-docs/schema-jackson-unknown-field-handling
- raw/official-docs/schema-protobuf-vs-json-evolution
본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
오류 기록 (본 feature 작업 중 발생)
- (없음 — 빌드/테스트 실패 없이 통과. Spring Boot 기본값이 contract 와 일치해 디버깅 세션 미발생.)
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- 후보 존재(별도 노트 미작성, branch note 로 충분): (1)
new BigDecimal(0.1)과new BigDecimal("0.1")의 차이와 ArchUnitcallConstructor(BigDecimal.class, double.class)로 정적 차단하는 법, (2) Spring Boot 가 이미 default false 인WRITE_DATES_AS_TIMESTAMPS를 굳이 명시 핀하는 이유(future default flip 회귀 차단 —spring.mvc.problemdetails.enabled=false와 동일 논리), (3)JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN가 client JSNumber정밀도 손실/scientific notation 과 어떻게 연결되는지, (4) datetime 직렬화 계약을ApplicationContextRunner로 effective bean 동작까지 테스트해JavaTimeModule누락 회귀를 잡는 패턴.
Blog topics (이 작업에서 파생된 글감)
- 후보(별도 노트 미작성): "Spring Boot serialization 계약을 '기본값'이 아니라 '명시 핀 + ArchUnit + effective-bean 테스트' 3중으로 고정하기" — 본 branch + sibling deser 측이 원석. 표준 근거는 raw/official-docs/rfc3339-datetime-utc + raw/official-docs/schema-bigdecimal-money-serialization-java.
관련 일일 노트
이 브랜치를 작업한 날짜들. 양방향 nav 유지.
- 2026-05-21 (initial scaffolding) — daily note 미생성
- 2026-05-22 (TODO drained, D1~D7 + G-F 외부근거 확정) — daily note 미생성
- 2026-06-02 (Phase C2 직렬화 출력측 구현: ArchUnit BigDecimal 룰 + 직렬화 핀 + 테스트 + 계약 문서) — daily note 미생성
완료 후 정리
- PR 링크: (미생성 — 사용자가 직접 커밋 예정)
- 리뷰 메모: ca-tmpl 3-stage code review chain 미실행(설정/테스트/문서 변경, Java 동작 로직 신규 없음). ArchUnit + serialization 테스트 + 전체
./gradlew testgreen 으로 검증. - 머지 결과 / 배포 환경: 로컬 검증까지(
locally-verified). dev/staging/prod 미배포. - wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목: ArchUnitno_bigdecimal_double_constructor룰 + 위반 fixture;spring.jackson.serialization.write-dates-as-timestamps=false+spring.jackson.generator.write-bigdecimal-as-plain=true핀(.env/application.yml/application-test.yml).locally-verified항목:JacksonSerializationPolicyTest(JacksonProperties 바인딩 + wired ObjectMapper 직렬화 동작), fixture 테스트(BigDecimal double 생성자 차단),verifyCleanArchitectureDependencies+:app-bootstrap:test+ 전체testBUILD SUCCESSFUL.prod-verified항목: (없음)
- 추출하지 않을 항목 (planned / documented-only / abandoned):
- D5 OpenAPI drift 집행(sibling), D6 제거-field 재사용 도구(
needs-confirmation), D7 Avro Schema Registry(범위 밖), money string-vs-number per-API 코드 시연(문서-only — sample 도메인 무변경), response field rename/versioning(feature-api-compatibility-deprecation-contract).
- D5 OpenAPI drift 집행(sibling), D6 제거-field 재사용 도구(