382 lines
40 KiB
Markdown
382 lines
40 KiB
Markdown
---
|
|
title: branch / feature-schema-serialization-contract
|
|
source_type: branch-note
|
|
status: verified
|
|
branch: feature-schema-serialization-contract
|
|
parent_branch:
|
|
related_projects: [ca-skeleton]
|
|
tags: [branch, ca-skeleton, schema, serialization, json]
|
|
created: 2026-05-21
|
|
last_reviewed: 2026-06-04
|
|
target_merge:
|
|
status_label: in-progress
|
|
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-015
|
|
kind: project-work-item
|
|
project: ca-skeleton-operational-contract
|
|
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-015
|
|
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-JSON-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: []
|
|
contract_packet: 1
|
|
contract_packet_sha256: 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`/`.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 기준을 정의합니다.
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
|
|
|
|
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: JSON·date·decimal serialization contract test가 통과한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| 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]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
날짜, 시간대, enum, 금액, null, unknown field 정책이 암묵적이면 API contract가 쉽게 깨집니다. skeleton은 serialization 기준과 schema drift 검증 기준을 가져야 합니다.
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- 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 직렬화 권장
|
|
- **검토한 대안**:
|
|
- **대안 1: Jackson default lenient** — `FAIL_ON_NULL_FOR_PRIMITIVES=false` default가 ca-tmpl null/empty/missing 분리와 **불일치** → 명시 override 필요
|
|
- **대안 2: Protobuf strict typing** — [[raw/official-docs/schema-protobuf-vs-json-evolution]] (wire-format + `reserved` field가 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, 별도 도구 도입
|
|
- **비교 핵심**: 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=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 = 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/.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.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` 을 자동 등록. 누락 회귀는 `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.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-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_TIMESTAMPS` default false + JavaTimeModule auto-register)하여, 동작 테스트는 첫 실행부터 green — 본 branch 의 가치는 "기본값 일치"가 아니라 **명시 핀으로 future default flip 회귀 차단** + 정적 BigDecimal 차단에 있음(D1/D2 의도와 정합).
|
|
|
|
## 묶음 (이 branch에서 파생된 자료)
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[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]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
<!-- GENERATED: blog-topics:start -->
|
|
- [[raw/blog-topics/spring-boot-serialization-contract-pins-2026-07-02]]
|
|
<!-- GENERATED: blog-topics:end -->
|
|
|
|
> 본 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 (이 작업에서 파생된 글감)
|
|
|
|
- 후보(별도 노트 미작성): "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 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`).
|