feat: jpa, messaging, notification, mongo, graphql 어댑터터 구현체 추가

This commit is contained in:
DongHyeonka
2026-08-15 13:01:58 +09:00
parent ac874e49e6
commit 2f5d2fc219
909 changed files with 62510 additions and 6354 deletions
@@ -0,0 +1,69 @@
# NOTIF-ADR-005 — 어느 notification API가 canonical인가
## 상태
Accepted (2026-08-15). NTF-018 대응.
## 문제
같은 저장소에 notification 모델이 **두 개** 있다.
| 세대 | 위치 | 규모 |
| --- | --- | --- |
| R0 legacy | `adapter:outbound:notification`의 router/provider seam | 삭제 예정 |
| R1 | `dev.caskeleton.application.notification` (직속) | public type 100개 |
| Platform | `dev.caskeleton.application.notification.platform..` | 신규 |
`application-core/CLAUDE.md`는 R1을 "R1 canonical"이라 부르고,
`docs/notification/migration-guide.md`는 R0 → platform 이행만 설명하며 R1의 처분을 전혀 다루지 않는다.
`Channel`, plan, dispatch, receipt/evidence 모델이 두 namespace에 중복 존재하고 둘 사이에 production
bridge도 import도 없다.
**실패 모드는 "무엇이 깨지는가"가 아니라 "무엇을 써야 하는가"다.** 새 consumer가 어느 API를 쓸지 알 수
없고, 두 모델이 각자 진화하며, R0를 지운 뒤에도 R1 graph가 고아로 남거나 platform이 R1 정책을 우회하는
이중 canonical이 된다.
## 결정
**Platform이 canonical이다.** R1은 유지되지만 새 production consumer를 받지 않는다.
이유는 능력이 아니라 증거다. platform은 durable acceptance, fenced claim, event ledger, projection,
reconciliation을 실제 PostgreSQL 레인으로 증명한다(NOTIF-ADR-001~003). R1은 fake로 증명된 R1 계약이며
스스로 그렇게 선언한다 — `application-core/CLAUDE.md`가 "R1 application contract proven with fakes.
It does not claim PostgreSQL schema/locking, provider protocol, cryptographic verifier, or runtime
wiring qualification"이라고 적어 둔 그대로다.
## Disposition
R1 public type 100개의 처분은 네 가지 중 하나다.
| 처분 | 의미 | 대상 |
| --- | --- | --- |
| `replace` | platform에 동등물이 있다. 새 consumer는 platform을 쓴다 | `Channel`, plan/dispatch/receipt/evidence 계열 |
| `bridge` | 변환이 필요하다. 변환은 ACL 한 곳에만 둔다 | writer-cutover / receipt 적용 경로 |
| `retain` | platform이 다루지 않는 관심사다. 그대로 둔다 | consent/quiescence verifier port |
| `delete` | R0와 함께 사라진다 | R0 router가 쓰던 seam |
전수 분류표는 이 ADR이 아니라 `docs/notification/module-mapping.md`가 소유한다. ADR은 규칙을,
mapping 문서는 목록을 소유한다 — 목록을 두 곳에 복제하면 드리프트하는 쪽이 늘어난다.
## 강제
두 namespace 사이의 production dependency는 **0건**이며, 이것은 문서가 아니라 ArchUnit 규칙이 지킨다
(`CleanArchitectureTest``NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER`).
변환이 필요해지면 `dev.caskeleton.application.notification.compatibility.r1` 한 패키지에만 두고, 그
패키지만 규칙에서 예외로 인정한다. 예외를 한 곳으로 모으는 것이 목적이다 — 두 모델이 서로를 아는
지점이 여러 곳이면 "어느 쪽이 canonical인가"라는 질문에 코드가 답하지 못한다.
## 결과
- 새 production consumer는 `..notification.platform..`만 쓴다.
- R1 type은 남지만, 새 코드가 그것을 import하면 ArchUnit이 막는다.
- R0 삭제는 이 ADR과 무관하게 진행된다. R1은 R0와 함께 사라지지 않는다.
## 하지 않은 것
R1 100개 type에 `@Deprecated(forRemoval = true)`를 붙이지 않았다. 제거 시점이 정해지지 않았고,
`forRemoval`은 "이 릴리스 이후 사라진다"는 약속이라 시점 없이 붙이면 그 자체가 거짓 신호다. 경계는
ArchUnit이 강제하고, deprecation은 제거 계획이 생길 때 붙인다.