ADR 0002: Canonical 데이터 strangler와 publication gate¶
상태: Historical — 초기 전환 검증에만 적용. 신규 구현은 direct canonical cutover 원칙을 따른다. 결정일: 2026-09-03 범위: 레거시 JSON 가져오기, canonical Catalog, revision/release, 조회 전환
Context¶
Procedure Hub의 47개 JSON은 화면 데이터이면서 사실상 원장 역할을 한다. 동일 시술은 M-code,
auto-* ID, 별칭, 자유 문장으로 혼재하고 제품은 taxonomy.json과 products.json에 중복된다.
기존 API는 이 레거시 식별자를 이미 외부 계약으로 사용하므로 새 DB의 UUID로 즉시 교체할 수
없다. 반대로 파일 구조를 그대로 테이블로 옮기면 안전·추천·근거의 의미가 다시 파일별로
분산된다.
실데이터 proof에서 118개의 legacy reference 중 115개는 기계적으로 매핑됐지만 다음 3개는 코드 prefix와 taxonomy 위치가 충돌했다.
M4-쥬베룩M8-서브시전M8-저출력레이저(두피)
2026-09-03 후속 검토에서 이 세 값은 legacy prefix가 아니라 taxonomy의 정확한 시술명과 부위 일치를 기준으로 canonical identity를 기술 확정했다. 현재 mapping registry의 pending 항목은 0개다. 이 결정은 임상 claim 또는 근거 승인을 의미하지 않는다.
또한 P9 흉터에는 서로 다른 하위 고민에서 같은 complaint 이름이 반복된다. 따라서 concern
전체에서 complaint 이름을 유일하게 만드는 초기 가정도 실제 데이터와 맞지 않았다.
Decision¶
1. 레거시와 canonical 저장 구조를 병행한다¶
기존 테이블과 API를 즉시 대체하지 않는다. 신규 테이블은 catalog_*, publication_* prefix로
병렬 생성하고, shadow parity가 통과한 read path부터 점진적으로 교체한다.
src/data 47 JSON + canonical mapping registry
│
├─ immutable SourceArtifact / draft ContentRevision
│
└─ validated canonical projection
├─ ProcedureConcept / Product
└─ Concern / Complaint
legacy read ─┐
├─ parity compare ──> canonical read cutover
new read ────┘
2. 원본 보존과 projection 가능 여부를 분리한다¶
모든 원본 payload와 mapping registry는 먼저 SourceArtifact와 ContentRevision에 손실 없이
저장한다. 해석이 보류된 reference가 있어도 원본 저장은 성공한다. 대신 해당 reference를
ImportMappingIssue에 기록하고 release publish를 차단한다. 임상적으로 확인되지 않은 매핑을
importer가 추측하지 않는다.
3. canonical UUID, public code, legacy code를 분리한다¶
- 내부 FK는 결정적 UUIDv5를 사용한다.
- 검증된 M/P-code는
public_code로 유지한다. auto-*, 한글 혼합 ID 같은 불안정 키는legacy_code에 그대로 보존한다.- 불안정 키의 canonical public code는
PC-{SHA256 prefix}로 결정적으로 만든다. - compatibility presenter는 cutover 기간에
legacy_code를 기존 응답 ID로 반환한다.
같은 입력 corpus는 항상 같은 artifact/revision/entity UUID와 content hash를 만든다.
4. 제품 병합은 field precedence와 충돌 이력을 함께 보존한다¶
화면 호환 필드는 products.json을 우선하고 taxonomy의 extracted 내용은 provenance로 남긴다.
서로 다른 값은 덮어써서 없애지 않고 field_conflicts에 source별로 기록한다. 현재 실데이터의
canonical 제품은 60개이고, 티엠버스는 타임버스의 검증된 alias다.
5. complaint 자연키는 하위 고민을 포함한다¶
Complaint의 자연키는 (concern, group_name, name_ko)다. 같은 시술명이 다른 임상 하위 고민에
등장할 수 있으므로 concern 전체 이름 유일 제약은 두지 않는다. 표시 순서는 concern 안에서
연속된 단일 순서를 유지한다.
6. 발행은 domain state machine과 DB transaction을 모두 통과한다¶
- revision:
draft → in_review → approved|rejected - release:
draft → published → superseded - release에는 domain key별 approved revision 하나만 포함한다.
- unresolved mapping issue가 하나라도 있으면 publish를 거부한다.
- 새 release 발행 시 기존 published release를 같은 transaction에서 supersede한다.
- rollback은 과거 상태 변경이 아니라 과거 snapshot을 이용한 새 revision/release다.
7. STG 직전에 migration을 squash한다¶
로컬 검증 중에는 변경 이유가 드러나는 migration을 허용한다. API parity와 import manifest가 확정된 STG 반영 시점에만 canonical migration들을 하나의 최종 baseline으로 합친다.
Consequences¶
2026-09-03 이후에는 짧은 점검 중단을 허용한다. 이미 작성되어 동작하는 legacy 경로를 선제 삭제하거나 다시 검증하지 않으며, legacy 병행 운용을 위한 신규 shadow·이중 쓰기·복귀 경로도 추가하지 않는다. 신규 기능은 canonical DB만 사용하고 외부 API의 기존 응답 DTO만 보존한다.
Positive¶
- 기존 API 의미를 유지한 채 새 모델을 실데이터로 검증할 수 있다.
- 원본, 정규화 결과, 보류 판단, 발행본을 추적할 수 있다.
- importer 재실행이 멱등이며 부분 적재는 명시적으로 거부된다.
- 임상 판단이 필요한 매핑을 기술 구현이 몰래 확정하지 않는다.
Cost¶
- cutover 전에는 legacy와 canonical 테이블이 함께 존재한다.
- alias ambiguity와 mapping issue를 운영자가 검토하는 절차가 필요하다.
- 신규 content 변경 importer는 initial baseline importer와 별도로 revision 번호와 diff를 계산해야 한다.
Rejected alternatives¶
| 대안 | 기각 이유 |
|---|---|
| 기존 파일별 테이블만 확장 | 도메인 의미와 중복이 그대로 남음 |
auto-*를 public code로 사용 |
생성 시각 기반 레거시 키가 새 외부 계약으로 고착됨 |
| 충돌 3건을 이름 유사도로 자동 확정 | 임상 분류와 코드 prefix 충돌을 기술 계층이 판단하게 됨 |
| 미확정 reference 때문에 원본 적재도 실패 | 감사 가능한 원문과 검토 대상을 함께 잃음 |
| 제품 충돌에서 한 source를 완전히 폐기 | taxonomy extracted 임상 정보와 변경 provenance가 유실됨 |