--- kind: CASE slug: a12-f002-jackson-databind title: 근거를 없앤 커밋이 README 를 열고 그 줄만 두었다 topic: messaging-and-outbox project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 rootTreeNode: case:a12-f002-jackson-databind body: case-a12-f002-jackson-databind.body.md evidenceCapturedOn: 2026-09-02 assets: - key: a12-f002-jackson-databind file: ../../../final/evidence/rendered/a12-f002-jackson-databind.svg - key: a12-f002-jackson-databind-history file: ../../../final/evidence/rendered/a12-f002-jackson-databind-history.svg evidence: - ../../../final/evidence/raw/a12-f002-jackson-databind.txt - ../../../final/evidence/raw/a12-f002-jackson-databind-history.txt source: - 원본 분석 절은 final/document.md#a12#L118 이다. 등급은 P3 이다. README 문장 인용, 잠금 파일에서 그 좌표가 컴파일과 실행 양쪽에 있다는 대조, 같은 리프의 검증 태스크가 그 모듈을 필수로 요구한다는 지적, 구세대 이름공간 쪽은 실제로 금지되어 있으므로 서술이 그 이름공간을 뜻했다면 맞지만 문장이 한정하지 않는다는 판단, 코드 결함이 아니라 근거로 적힌 사실이 무너진 것이라는 결론이 그 절에 있다. - 이 기록이 더한 것은 넷이다. 같은 주장이 클래스 자바독에도 있어 고칠 자리가 둘이라는 것. 잠금 파일이 아니라 이 리프의 빌드 선언이 그 좌표를 끌어온다는 것. 구성을 바꾼 커밋이 2026-07-31 이고, 그 커밋이 같은 자리에서 README 에 절 셋을 붙이면서 이 줄만 두고 갔으며 그 뒤로 이 파일을 연 커밋이 없다는 것. 그리고 그 라이브러리로 봉투를 만드는 옆 파일이 있지만 그것을 부르는 어댑터를 조립하는 곳은 없다는 것이다. --- # 근거를 없앤 커밋이 README 를 열고 그 줄만 두었다 README 가 손수 짠 직렬화의 근거로 결속 라이브러리가 클래스패스에 없다는 사실을 들고, 그 클래스의 자바독이 같은 문장을 다시 적는다. 그 좌표를 배포되는 클래스패스로 올린 커밋이 같은 자리에서 README 에 절 셋을 붙이면서 이 줄만 두고 갔다. ## 관계 - **매 PR 을 막는 게이트가 초록일 수 없다** 같은 리프의 같은 잠금 좌표에서 갈라진 사례다. 저쪽은 판본이 올라간 커밋, 이쪽은 구성이 바뀐 커밋이다. - **README가 "노출된 setting도 bean도 없다"고 적은 능력에 production bean 여덟이 있다** 두 사례 모두 README 가 적은 것과 실제 코드가 다르다. - **문서가 지목하는 조정 레코드를 쓰는 코드가 없다** 문서가 가리킨 대상이 코드에 없다는 점이 같다. ## 문제 봉투 직렬화를 손으로 짠 이유가 README 에 적혀 있다. 이 모듈은 결속 라이브러리를 클래스패스에 두지 않아 뼈대를 가볍게 유지하며, 그래서 봉투 직렬화는 의존성 없는 손수 짠 JSON 이라는 것이다. ## 결론 손수 짠 클래스는 그 서술과 어긋나지 않는다. 바깥에서 끌어오는 타입이 없다. 다만 그 클래스의 자바독이 README 와 같은 문장을 다시 적는다. 모듈이 그 라이브러리를 클래스패스 밖에 둔다는 것이다. 고칠 자리가 하나가 아니라 둘이다. 잠금 파일만의 문제도 아니다. 이 리프의 빌드 파일이 스프링 JSON 스타터와 스키마 검증기를 직접 선언한다. 그 좌표가 컴파일과 실행 클래스패스에 올라 있는 것은 그 선언의 결과다. 같은 빌드 파일의 검증 태스크는 그 좌표가 런타임 그래프에 있어야 한다고 요구한다. 문서가 부재를 근거로 삼는 동안 게이트는 존재를 조건으로 건다. 문장이 쓰일 때는 맞았다. 초기 커밋 시점에는 그 좌표가 배포되는 클래스패스 어디에도 없었다. 바꾼 것은 2026-07-31 의 메시징 R2 커밋이다. 그 커밋이 빌드 파일에 JSON 스택 둘을 선언하면서 좌표를 컴파일과 실행으로 올렸다. 같은 커밋이 README 를 열어 절 셋을 새로 붙였고, 근거가 무너진 그 문장만 그대로 두었다. 그 뒤로 이 README 를 건드린 커밋은 없다. 실시간 팬아웃 봉투는 석 주 뒤에 들어왔다. 883 파일을 건드린 커밋이 이 리프에서 만진 것은 잠금 파일과 팬아웃 어댑터와 그 봉투 클래스 셋이고, 잠금 파일에서 바뀐 것은 판본뿐이다. 문장이 근거로 삼은 것은 모듈 전체의 부재이고, 그것이 깨진 뒤 옆 파일이 봉투 JSON 조립을 그 라이브러리로 처리한다. 다만 그 봉투를 부르는 어댑터를 조립하는 곳이 저장소 어디에도 없다. 선언은 그대로 있고 그것을 부르는 실행 경로만 없다. 구세대 이름공간의 결속과 코어를 세우는 것은 검증 태스크 안의 단언 한 줄이고, 구성 단위 제외 목록이 빼는 것은 형식 계열뿐이다. 애너테이션 쪽 2.21 은 아직 두 클래스패스에 다 남아 있다. 서술이 그 이름공간을 뜻했다면 맞지만, 문장에 이름공간이 없다. 판정은 P3 다. 코드 결함은 아니다. README 를 읽고 이 리프의 의존성 정책을 판단하는 사람은 지금 없는 사실을 근거로 삼게 된다. ## 검증 환경 Gradle : 9.0.0 확인 방식 : README 와 자바독 대조, 빌드 선언과 잠금 파일 확인, 리프 main 의 참조 전수 확인, 커밋 이력 조회 소스 수정 : x ## 재현 조건 1. README 의 직렬화 근거 문장과 손수 짠 클래스의 자바독을 나란히 읽는다. 2. 빌드 파일에서 그 라이브러리를 끌어오는 선언을 찾는다. 3. 같은 빌드 파일의 검증 태스크가 요구하는 좌표를 읽는다. 4. 리프 main 에서 그 라이브러리를 쓰는 곳을 임포트와 인라인 표기까지 전수로 찾는다. 5. 그중 봉투를 만드는 쪽의 본문을 읽고, 그 봉투와 어댑터를 자기 파일 밖에서 부르는 곳을 센다. 6. 잠금 파일에서 관련 좌표를 확인한다. 7. 그 좌표의 구성이 바뀐 이력을 커밋별로 뽑는다. 8. README 를 건드린 커밋 전부를 나열하고, 구성을 바꾼 커밋이 README 에 무엇을 했는지 그 앞뒤 36 행으로 확인한다. 9. 봉투 클래스가 들어온 커밋과 그 커밋이 이 리프에서 건드린 파일을 확인한다. ## 본문 봉투 직렬화를 손으로 짠 이유가 README 에 적혀 있고, 그 클래스의 자바독도 같은 말을 한다. ## 같은 주장을 하는 두 자리 :::evidence key="a12-f002-jackson-databind" alt="README 의 근거 문단과 손수 짠 클래스의 자바독이 같은 주장을 하는 것, 빌드 파일이 그 라이브러리를 끌어오는 선언과 같은 파일의 검증 태스크가 요구하는 좌표, 리프 main 에서 그 라이브러리를 쓰는 곳을 임포트와 인라인 표기까지 전수로 찾은 목록, 그중 봉투를 만드는 쪽의 본문, 그 봉투와 어댑터를 자기 파일 밖에서 부르는 곳의 수, 그리고 잠금 파일의 관련 좌표를 출력한 터미널 기록." caption="README 와 클래스 자바독이 같은 문장을 적는다 · 빌드 파일이 스프링 JSON 스타터와 스키마 검증기를 선언하고, 같은 파일의 검증 태스크는 그 좌표를 필수로 든다 · 리프 main 의 두 파일이 그 라이브러리를 쓰고 한쪽은 봉투를 만든다 · 그 봉투를 부르는 어댑터를 조립하는 곳은 0 — 44줄 · exit 0" zoom="true" ::: ```text ## OutboxEnvelopeJson — 손수 짠 JSON 이 모듈은 `jackson-databind` 를 classpath 에 두지 않아(스켈레톤을 가볍게 유지) outbox envelope 직렬화는 의존성 없는 손수 짠 JSON 이다. ``` ```java /** * Hand-rolled, dependency-free JSON serialiser for the outbox envelope (no Jackson — the module * deliberately keeps {@code jackson-databind} off its classpath). ``` 손수 짠 코드 자체는 서술대로다. 임포트가 도메인 타입 하나뿐이다. 고쳐야 할 것은 그 코드가 아니라 두 자리에 적힌 근거다. ## 빌드 파일이 그것을 끌어온다 ```text 8: implementation 'org.springframework.boot:spring-boot-starter-json' 9: implementation('com.networknt:json-schema-validator:3.0.2') { ``` 잠금 파일이 스스로 그렇게 된 것이 아니다. 이 리프가 직접 선언한다. 그리고 같은 파일 아래쪽의 검증 태스크가 그 좌표를 요구한다. ```groovy 'com.networknt:json-schema-validator:3.0.2', 'tools.jackson.core:jackson-core:3.0.2', 'tools.jackson.core:jackson-databind:3.0.2' ``` README 가 없다고 적은 모듈을 이 리프의 게이트가 필수로 든다. ## 문장이 무너진 자리 :::evidence key="a12-f002-jackson-databind-history" alt="이 좌표의 구성이 바뀐 이력을 커밋별로 뽑은 목록, README 를 건드린 커밋 전부, 구성을 바꾼 커밋이 README 에 절 셋을 붙였다는 것과 그 커밋 앞뒤의 36 행, 그리고 봉투 클래스가 들어온 커밋과 그 커밋이 이 리프에서 건드린 파일을 출력한 터미널 기록." caption="초기 커밋에서는 시험 클래스패스에만 있었고, 2026-07-31 커밋이 컴파일과 실행으로 올렸다 · 그 커밋이 README 에 절 셋을 붙였는데 36 행은 앞뒤가 같다 · 그 뒤 README 를 건드린 커밋은 없다 · 봉투 클래스는 석 주 뒤 커밋이 들여왔고 그 커밋의 잠금 변경은 판본뿐 — 29줄 · exit 0" zoom="true" ::: ```text # 이 좌표의 구성이 바뀐 이력 a24ece9c 2026-08-28 feat: web, websocket 어댑터 추가 구현 -tools.jackson.core:jackson-databind:3.0.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +tools.jackson.core:jackson-databind:3.1.5=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath 5f10b791 2026-08-11 chore: record pre-existing uncommitted repository state e5af2912 2026-07-31 feat: add messaging R2 polling producer -tools.jackson.core:jackson-databind:3.0.2=testCompileClasspath,testRuntimeClasspath +tools.jackson.core:jackson-databind:3.0.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath 821fe00c 2026-07-24 init: 클린 아키텍처 백엔드 +tools.jackson.core:jackson-databind:3.0.2=testCompileClasspath,testRuntimeClasspath ``` 구성을 바꾼 것은 2026-07-31 커밋이다. 8월 커밋이 한 일은 판본을 올린 것뿐이다. 문장의 classpath 를 배포되는 쪽으로 읽으면 초기 커밋 때는 성립했다. 이름공간을 두고는 같은 호의를 주지 않는다는 것이 아래의 문제다. ## 그 커밋이 README 에 한 일 ```text # 구성을 바꾼 커밋이 README 에 한 일 README 를 건드렸는가 : 1 그 커밋이 README 에 새로 붙인 절 : 3 개 그 커밋 앞뒤의 36 행 before 이 모듈은 `jackson-databind` 를 classpath 에 두지 않아(스켈레톤을 가볍게 유지) outbox envelope after 이 모듈은 `jackson-databind` 를 classpath 에 두지 않아(스켈레톤을 가볍게 유지) outbox envelope ``` 같은 커밋이 이 README 를 열어 절 셋을 새로 붙였다. 방금 자기가 무너뜨린 그 한 줄만 그대로 두었다. ```text # README 를 건드린 커밋 전부 e5af2912 2026-07-31 feat: add messaging R2 polling producer b3add016 2026-07-28 feat: redis, fileserver, httpclient 런타임 시점 구현 추가 821fe00c 2026-07-24 init: 클린 아키텍처 백엔드 ``` 그 뒤로 이 파일을 연 커밋은 없다. ## 같은 리프에서 그 라이브러리를 쓰는 곳 ```text realtime/RealtimeFanoutEnvelopeJson.java:5:import tools.jackson.databind.ObjectMapper; realtime/RealtimeFanoutEnvelopeJson.java:6:import tools.jackson.databind.json.JsonMapper; realtime/RealtimeFanoutEnvelopeJson.java:7:import tools.jackson.databind.node.ObjectNode; realtime/RealtimeFanoutEnvelopeJson.java:71: private static String text(tools.jackson.databind.JsonNode root, String field) { envelope/LocalJsonSchemaRegistry.java:33:import tools.jackson.databind.DeserializationFeature; envelope/LocalJsonSchemaRegistry.java:34:import tools.jackson.databind.JsonNode; envelope/LocalJsonSchemaRegistry.java:35:import tools.jackson.databind.ObjectMapper; envelope/LocalJsonSchemaRegistry.java:36:import tools.jackson.databind.json.JsonMapper; ``` README 가 근거로 든 것은 모듈 차원의 부재다. 그 부재가 깨진 자리에서 같은 리프의 옆 파일이 같은 종류의 일을 그 라이브러리로 한다. ```java 24: private static final String VERSION = "1"; 25: private static final ObjectMapper MAPPER = JsonMapper.builder().build(); 30: public static String toJson(DurableFanoutRecord record) { 31: Objects.requireNonNull(record, "record must not be null"); 32: ObjectNode root = MAPPER.createObjectNode(); ``` 봉투 JSON 을 조립하는 일이다. ## 다만 그 경로는 아직 돌지 않는다 ```text RealtimeFanoutEnvelopeJson 을 자기 파일 밖에서 부르는 곳 : 1 MessagingDurableFanoutAdapter 를 자기 파일 밖에서 부르는 곳 : 0 ``` 부르는 하나는 팬아웃 어댑터이고, 그 어댑터를 조립하는 곳은 없다. 클래스패스 사실은 그대로이고, 도는 경로만 아직 없다. ## 이름공간을 한정하지 않는다 구세대 이름공간의 결속과 코어를 막는 것은 빌드 파일 검증 태스크의 단언이다. 구성 단위의 제외 목록은 형식 계열만 뺀다. ```text 8:com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath ``` 같은 이름공간의 애너테이션은 지금도 컴파일과 실행에 있다. 서술이 결속과 코어만 뜻했다면 맞지만, 문장에 이름공간이 없다. ## 확인하지 못한 것 실시간 팬아웃 봉투가 손수 짠 쪽과 같은 규율을 따라야 하는지는 판단하지 않았다. 여기서 확인한 것은 문장이 서술하는 사실 관계까지다.