adapter-identifier — 설계 결정 참조
비-IO 인프라 능력(capability) 어댑터 모듈. 패키지 루트: dev.caskeleton.adapter.identifier.
허용/금지 의존과 테스트 명령 같은 모듈 규칙은 CLAUDE.md 가 SSOT 다. 이 문서는 코드 주석에서 덜어낸 설계 결정의 근거를 모아둔 참조용 기록이다 — 코드를 읽다 "왜 이렇게 했나"가 궁금할 때 본다.
왜 별도 모듈인가 (adapter-outbound 와의 구분)
adapter-outbound 처럼 도메인 포트를 구현하는 driven/secondary 어댑터지만, 외부 시스템 연동이
없는(no external-system integration) 능력만 담는다: 식별자 생성/인코딩(UUIDv7), 같은 근거로 clock·
crypto/random 소스. UUID id/코덱 능력은 인프라이지 아웃바운드 연동 지점이 아니므로, 이것을
adapter-outbound 밖에 둬야 그 모듈의 문서화된 의미("외부 HTTP / messaging / cache / notifications")가
유지된다.
UuidCodec
도메인 무관 UUID 변환 유틸. JDK java.util.UUID(RFC 9562 UUIDv7) 위에서 동작한다.
normalize(String)— D3: 대소문자 무관 canonical UUID 입력을 받아 canonical 36자 소문자 형태로 반환. 형식 오류 UUID 에는IllegalArgumentException.null입력은null반환.toUuid(String)— D10: UUID 문자열 → 128-bitUUID(PostgreSQLuuid컬럼용).fromUuid(UUID)— D10: 저장된UUID→ canonical 36자 소문자 UUID 문자열.
HmacUserPrincipalPseudonymizer
UserPrincipalPseudonymizerPort(application-core) 의 HMAC-SHA-256 구현.
알고리즘 SSOT
구체 알고리즘은 90일 회전 salt 로 키잉한 HMAC-SHA-256 이다. 이 클래스가 유일한 구현이며,
비-IO crypto 능력 어댑터로 이 모듈에 있고 app-bootstrap
이 싱글톤 빈으로 와이어링한다.
출력
비어있지 않은 rawPrincipal 에 대해 단방향·안정적인 256-bit HMAC 토큰을 64자 소문자 hex 로 반환.
rawPrincipal 이 null 이거나 blank 면 null 반환.
Salt 출처
salt 는 app-bootstrap 이 APP_PRIVACY_PSEUDONYMIZATION_SALT 환경변수에서 공급한다(분류: secret,
회전 주기: 90일). 이 클래스는 salt 를 스스로 조달하지 않는다.
Thread safety
Mac 인스턴스는 thread-safe 하지 않다. 매 pseudonymize(String) 호출마다 새 Mac 을 생성하므로
공유 싱글톤 빈으로 안전하다. HmacSHA256 은 JDK 필수 알고리즘(JCA spec)이라 NoSuchAlgorithmException·
InvalidKeyException 은 사실상 도달 불가능하며, 호출부에 checked exception 잡음을 남기지 않으려고
IllegalStateException 으로 감싼다.
Spring-free
이 모듈(adapter-identifier)은 설계상 Spring-free 다. 어노테이션이 없고, 빈 생성은
app-bootstrap 의 책임이다.
빌드 결정 (build.gradle)
Groovy / Spock (C2 테스트 형태)
순수 값-코덱 동작(UuidCodec)은 Groovy/Spock 스펙(src/test/groovy)으로 명세한다. core groovy
플러그인이 컴파일하고, 모든 서브프로젝트에 이미 켜진 JUnit Platform(useJUnitPlatform())에서 실행된다.
가드/계약 테스트(HmacUserPrincipalPseudonymizerTest — 생성자 가드, 정확한 예외/포맷 계약)는 설계상
Java(src/test/java)로 둔다. Spock 2.4 / Groovy 4.0 variant 를 쓰며, spock-core 가 groovy.jar 를
transitive 로 끌어오므로 data-driven where: 스펙에 다른 Groovy 모듈이 필요 없다.
implementation vs api
:application-core 를 implementation 으로 선언한다(api 아님). adapter-identifier 가 자신의 public
ABI 에 application-core 타입을 노출하지 않기 때문이다. 유일한 와이어링 소비자인 app-bootstrap 은
이미 자기 classpath 에 application-core 를 갖고 있다. 이 의존 edge 는 src/build.gradle 의
allowedProjectDependencies['adapter-identifier'] 로 허용된다.
UTF-8 인코딩 고정
한국어(비-ASCII) Spock 스펙 메서드명은 소스를 UTF-8 로 읽어야만 컴파일·리포팅이 정상이다. 이 모듈이
비-ASCII 소스를 처음 갖는 모듈이라 컴파일 인코딩을 명시적으로 고정한다 — UTF-8 호스트에선 no-op 지만,
플랫폼 기본이 다른 fork(예: 한국어 Windows / MS949)에서 mojibake 빌드를 막는다. C2 가 더 많은 모듈로
퍼지면 root subprojects 블록(-parameters 옆)으로 승격한다.