init: 클린 아키텍처 백엔드

This commit is contained in:
DongHyeonka
2026-07-24 14:29:36 +09:00
parent 9eed16d097
commit 821fe00c32
971 changed files with 74769 additions and 1 deletions
@@ -0,0 +1,34 @@
# adapter:outbound:notification — 설계 결정 참조
알림(email/Slack 등) 아웃바운드 어댑터 모듈. 패키지 루트:
`dev.caskeleton.adapter.outbound.notification`. `:adapter:outbound:support` 에 의존해 공유
correlation / fail-open 의존성 로깅을 재사용한다.
허용/금지 의존 정책은 `src/build.gradle`
`allowedProjectDependencies['adapter:outbound:notification']` 항목이 SSOT 다(이 모듈은 아직
별도 CLAUDE.md 를 두지 않았다). 이 문서는 코드 주석에서 덜어낸 **설계 결정의 근거**를 모아둔
참조용 기록이다.
## 모듈 개요
application-core 포트 뒤에 두는 **선택형** 알림 어댑터다. `@ConditionalOnProperty` 로 게이팅되고
기본 비활성이다. 이 모듈이 기본 제공하는 프로바이더는 `email/google`(`GoogleEmailProvider` /
`GoogleEmailClient`)과 `slack/webhook`(`SlackWebhookProvider` / `SlackClient`)이며, 실제 연동
client 는 포킹 프로젝트가 채우는 seam 이다.
## (channel, providerId) 복합 키 + fan-out
`RoutingNotifier``(channel, providerId)` 복합 키로 프로바이더를 등록한다 — 채널 내 중복
`providerId` 는 생성 시점에 실패한다. 라우트당 providerId 목록을 주면 fan-out(모든 프로바이더
호출)이 된다. 각 프로바이더는 이미 `FailOpenNotificationProvider` 로 감싸져 있어 한 곳의 실패가
다른 곳을 막지 않고, 그래서 fan-out 루프에 try/catch 가 필요 없다. `FailOpenNotificationProvider
.send``throws` 를 선언하지 않는 건 이 루프를 try/catch 없이 예외-free 로 증명하기 위함이다.
## 중앙 fail-open 합성 + 라우팅 바인딩
`NotificationConfig` 가 모든 프로바이더를 `FailOpenNotificationProvider` 로 중앙에서 감싼다(→
`:adapter:outbound:support``FailOpenDependencyLogger` 로 WARN 로깅). 라우팅은
`app.notification.routes.<channel>.<route>=<providerId>[,<providerId>]`. 프로바이더는
`channel()`+`providerId()` 로 키잉된 `NotificationProvider` 빈으로 기여한다(예:
`GoogleEmailProvider`, `SlackWebhookProvider`). `GoogleEmailClient`/`SlackClient` 는 포크가
구현하는 seam 이며 실패는 데코레이터가 fail-open 처리한다.