Files
llm-wiki/raw/branch-notes/feature-schema-serialization-contract.md

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
ca-skeleton
branch
ca-skeleton
schema
serialization
json
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
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-JSON-001@1
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_constructor ArchUnit 룰(CleanArchitectureTest, callConstructor(BigDecimal.class, double.class)/float.class), BigDecimalDoubleConstructorFixture + ArchitectureViolationFixtureTest, JacksonSerializationPolicyTest(JacksonProperties 바인딩 + wired ObjectMapper 직렬화 동작: OffsetDateTime"1985-04-12T23:20:50.52Z", LocalDate"2026-06-02", new BigDecimal("1.10")1.10, 대형 값 비-scientific), application.yml/application-test.yml/.envspring.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 기준을 정의합니다.

부모 (필수)

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):
  • 검토한 대안:
    • 대안 1: Jackson default lenientFAIL_ON_NULL_FOR_PRIMITIVES=false default가 ca-tmpl null/empty/missing 분리와 불일치 → 명시 override 필요
    • 대안 2: Protobuf strict typingraw/official-docs/schema-protobuf-vs-json-evolution (wire-format + reserved field가 ca-tmpl JSON 환경에서 가장 크게 보강할 부분)
    • 대안 3: Avro schema evolutionraw/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, 별도 도구 도입
  • 비교 핵심: 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_UP unless 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-fields extension 또는 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=trueD3 + SBMS-C4 (지수 표기 회피)

  • UNSUPPORTED_IMPL_DECISION: ①위 설정을 application.ymlspring.jackson.* property 로 둘지 Jackson2ObjectMapperBuilderCustomizer 빈으로 둘지의 wiring 위치 선택 — 근거 raw 는 property 의미만 권고하고 적용 메커니즘은 권고하지 않음. trade-off: property = 선언적·테스트 용이 / customizer = @JsonComponent 등 복합 설정과 일관. → property 기본 + 복합 설정 시 customizer 보강 으로 사용자 임의 채택. ②READ_UNKNOWN_ENUM_VALUES_AS_NULLspring.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 = JS Number 정밀도 손실 위험.
  • 기본 선택 기준 (사용자 임의 trade-off): 외부 노출 / 금융 / public API = string (client 정밀도 안전 우선), 내부 서비스 간 API = number + plain (파싱 비용 절감). API 별 한 가지를 OpenAPI 에 명시 의무 (Decisionized Work Items money/decimal row 의 "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 Items enum unknown row

  • null / empty / missing 의미 분리를 mapper 가 소유 → D1 + SJUF-C2 (Jackson default 가 분리 안 함) + Decisionized Work Items null/empty/missing row

  • 레이어 경계: 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> vs Optional<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-fields extension 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-C2 Does-not-prove) 이나 ca-tmpl 운영 정책은 "서버 timezone = UTC" 강제 — offset 비-Z 출력 발생 시 운영 정책 위반으로 판정 필요.
    • date-only calendar field: offset datetime 강제에서 제외 (Decisionized Work Items date/time row 의 allowed). LocalDate vs OffsetDateTime 혼용 시 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 참조).
  • 다른 계약 의존:
    • 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>)는 sibling feature-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), ② wired ObjectMapper 동작 assert(OffsetDateTime"1985-04-12T23:20:50.52Z", LocalDate"2026-06-02", new BigDecimal("1.10")1.10, 대형 값 비-scientific).
  • src/.envJackson (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.ymlspring.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(.envapplication.yml spring.jackson.*)를 그대로 따라 property + env 키 채택. 복합 직렬화기가 필요해지면 그때 Jackson2ObjectMapperBuilderCustomizer 보강.
  • WRITE_BIGDECIMAL_AS_PLAIN property key: Spring Boot spring.jackson.generator.*JsonGenerator.Feature 바인딩. JacksonProperties.getGenerator() 로 effective 확인.
  • JavaTimeModule: 별도 명시 등록 안 함 — Spring Boot starter-json auto-config 가 classpath 의 jackson-datatype-jsr310 을 자동 등록. 누락 회귀는 JacksonSerializationPolicyTestOffsetDateTime 직렬화 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.md S2 에 문서화. 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 SUCCESSFUL
  • cd src && ./gradlew :app-bootstrap:test → BUILD SUCCESSFUL (신규 JacksonSerializationPolicyTest 2건 + fixture 테스트 1건 포함)
  • cd src && ./gradlew test → BUILD SUCCESSFUL (전체 모듈)

Claims To Verify 상태 변화

Claim 이전 이후
모든 응답에서 timezone 없는 datetime 미발생 planned 부분 locally-verifiedWRITE_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-verifiedno_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_TIMESTAMPS default false + JavaTimeModule auto-register)하여, 동작 테스트는 첫 실행부터 green — 본 branch 의 가치는 "기본값 일치"가 아니라 명시 핀으로 future default flip 회귀 차단 + 정적 BigDecimal 차단에 있음(D1/D2 의도와 정합).

묶음 (이 branch에서 파생된 자료)

본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.

오류 기록 (본 feature 작업 중 발생)

  • (없음 — 빌드/테스트 실패 없이 통과. Spring Boot 기본값이 contract 와 일치해 디버깅 세션 미발생.)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • 후보 존재(별도 노트 미작성, branch note 로 충분): (1) new BigDecimal(0.1)new BigDecimal("0.1") 의 차이와 ArchUnit callConstructor(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 JS Number 정밀도 손실/scientific notation 과 어떻게 연결되는지, (4) datetime 직렬화 계약을 ApplicationContextRunner 로 effective bean 동작까지 테스트해 JavaTimeModule 누락 회귀를 잡는 패턴.

Blog topics (이 작업에서 파생된 글감)

관련 일일 노트

이 브랜치를 작업한 날짜들. 양방향 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 test green 으로 검증.
  • 머지 결과 / 배포 환경: 로컬 검증까지(locally-verified). dev/staging/prod 미배포.
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목: ArchUnit no_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 + 전체 test BUILD 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).