11 KiB
11 KiB
title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
| title | source_type | status | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | branch | parent_branch | related_projects | tags | created | target_merge | status_label | contract_packet_sha256 | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-messaging-multibroker-router | branch-note | raw | BR-CA-SKELETON-OPERATIONAL-CONTRACT-053 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-053 |
|
|
1 | feature-messaging-multibroker-router |
|
|
2026-06-16 | in-progress | 6d5d448a4d1f5608639023b6bacf14a05e5491283794a7c796714e678b5c3cf3 |
branch: feature-messaging-multibroker-router
Layer:
raw/branch-notes/— 단일 브랜치의 TODO·결정·진행 기록. git 작업은develop브랜치에서 수행.
부모 (필수)
raw/project-notes/ca-skeleton-operational-contract
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: broker 선택·routing·fallback과 core transport-neutrality test가 명시된다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-EVENT-BROKER-001@1 |
core는 broker-agnostic outbox를 제공하고 Kafka는 optional adapter로 둔다 | broker router가 core transport-neutrality와 optional adapter 경계를 유지하도록 적용한다 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1 |
Gradle multi-module에서 domain-core·application-core·adapter-*·shared-contract·app-bootstrap·sample-portfolio 책임을 분리한다 | router SPI·adapter·bootstrap wiring의 module ownership에 적용한다 | raw/project-notes/ca-skeleton-operational-contract |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
없음.
목표
adapter-outbound 메시징 리뷰에서 "outbox 켜면 Kafka 강제 + 브로커 추가 시 config 편집 필요"가 확인됨(cache 는 '파일만 추가'인데 메시징은 Kafka-binary). 메시징 선택/와이어링을 cache 라우터 패턴으로 이식해 브로커 추가 = 파일만 추가(중앙 config·SPI·제너릭 데코레이터 불변)로 만든다. 포트 시그니처·메시지 매핑·fail-open/closed 실패 계약은 보존.
설계: ca-tmpl/docs/superpowers/specs/2026-06-16-messaging-multibroker-design.md. 계획: .../plans/2026-06-16-messaging-multibroker-plan.md. (둘 다 gitignored)
범위
포함 범위
- 신규 SPI
MessageBroker(brokerId+send) + 제너릭 데코레이터OutboundMessagePublisher(fail-open)·OutboxMessagePublishAdapter(fail-closed) + 중앙MessagingConfig+MessagingSettings(app.messaging.broker) + 포트별DisabledMessagePublisher/DisabledOutboxMessagePublisher. - Kafka 를 기여자로 전환:
KafkaMessageBroker(implements MessageBroker),KafkaAdapterConfig(@ConditionalOnProperty app.messaging.broker=kafka + @EnableConfigurationProperties),KafkaAdapterSettings(enabled 제거, brokers format-only). - 삭제:
KafkaMessagePublisher,KafkaOutboxMessagePublishAdapter,Disabled{Message,OutboxMessagePublish}*(kafka/outbox 위치),OutboxPublishAdapterConfig. - 속성 교체:
app.messaging.kafka.enabled/APP_MESSAGING_KAFKA_ENABLED→app.messaging.broker/APP_MESSAGING_BROKER(.env/application.yml/env-keys.yaml).APP_MESSAGING_KAFKA_BROKERS유지. - 테스트 5개 재작성/갱신.
- 부수:
code-conventions.md에 P1/P2(패키지 구조) 규칙 추가; 로거 통합 검토(결론: 분리 유지).
제외 범위
- 다중 동시 브로커(per-topic 라우팅) — 단일 활성으로 결정.
- 실제 Kafka SDK — seam(KafkaSender/KafkaMessageBroker) 유지.
- 두 outbound 로거 통합 — 검토 후 의도적 분리 유지(아래 D7).
근거
| Source | 정당화하는 결정 |
|---|---|
cache 멀티백엔드 라우터 (CacheBackend/CacheStoreRouter/CacheRouterConfig, adapter-outbound) |
SPI+중앙조립+데코레이터 패턴 이식 |
OutboxMessagePublishPort javadoc (application-core) |
fail-closed 보존 불변식 |
KafkaMessagePublisher 기존 동작 |
fail-open(swallow) 보존 불변식 |
Spring @ConditionalOnProperty/@ConfigurationPropertiesScan |
브로커 자기등록 게이팅, settings 전역 바인딩 처리 |
./gradlew check 1254 pass (2026-06-16) |
행위 보존 검증 |
결정-근거 매핑
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | 단일 활성 브로커(app.messaging.broker=<id>) |
통상 브로커 1개 / per-topic 다중이면 cache식 bindings | 사용자 결정 2026-06-16 | user-directed |
per-topic 다중 브로커 필요 시 재설계 |
| D2 | 통합 단일 MessageBroker SPI(일반+outbox 공용) |
두 포트가 "선택 브로커로 전송" 공유 | 사용자 결정; 설계 §4 | user-directed |
없음(테스트 green) |
| D3 | 환경 플래그 깨끗한 교체 | 스켈레톤(레거시 사용자 없음) / 배포중이면 alias | 사용자 결정; verifyEnvKeys green | user-directed + verified |
fork 가 옛 키 쓰면 깨짐(스켈레톤이라 무관) |
| D4 | fail-open/closed 를 바인딩 레벨 데코레이터로 분리 보존 | 두 실패 계약이 정반대(swallow vs rethrow) | OutboxMessagePublishPort javadoc; KafkaMessagePublisher 코드 |
code-evidence + verified |
없음 |
| D5 | Disabled 를 포트별 2클래스로 분리(통합 1클래스 폐기) | 1클래스가 두 포트 구현 시 getBean(MessagePublisher) 모호 |
NoUniqueBeanDefinitionException(테스트가 포착) | verified (버그→수정→green) |
없음 |
| D6 | P1/P2 패키지 규칙을 code-conventions SSOT 에 성문화 | 관례는 있으나 규칙 부재 시 | 사용자 요청; adapter-outbound/CLAUDE.md dominant |
user-directed |
ArchUnit 미강제(문서+리뷰) |
| D7 | 두 outbound 로거 분리 유지(통합 안 함) | 필드셋·로그레벨 정책·SSOT 가 다를 때 | 코드 비교(아래 Claims) | code-evidence |
통합 안 해 약간의 형식 중복(2줄) 잔존 |
검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| 전 리네임/구조변경이 행위 보존 | 교차모듈 구조 변경 | ./gradlew check 전체 green |
locally-verified (1254/1254) |
| fail-open(일반 swallow)·fail-closed(outbox rethrow) 둘 다 보존 | 데코레이터 분리 | OutboundMessagePublisherTest·OutboxMessagePublishAdapterTest green | locally-verified |
| 브로커 추가 = 파일만(중앙 불변) | 실제 2번째 브로커 미추가 | RabbitMessageBroker+RabbitAdapterConfig 추가 시 MessagingConfig 무변경 확인 | needs-confirmation |
| env 깨끗한 교체가 정합 | 3-way(.env/yaml/registry) | verifyEnvKeys green + 옛 키 grep 0 | locally-verified |
| 두 로거 분리가 정당(통합 부적절) | 유사 이름 | 필드셋(operation vs duration_ms/retry_attempt)·로그레벨(WARN-always vs WARN/ERROR)·SSOT(mdc-keys vs metrics.yaml) 상이 확인 | code-verified |
| 정식 CA 리뷰 체인 통과 | 인라인 구현 | 커밋 후 ca-architect-sentinel→spec→quality | needs-confirmation |
검증
./gradlew check→ BUILD SUCCESSFUL, 1254 test pass / 0 fail. verifyEnvKeys·verifyOneTypePerFile·verifyCleanArchitectureDependencies·ArchUnit(Clean+Naming) green.- 중간 버그: 통합
DisabledMessaging(2포트 구현)이getBean(MessagePublisher)모호성 유발 → 테스트가 포착 → 포트별 2클래스로 분리 후 green. - 옛 플래그(
APP_MESSAGING_KAFKA_ENABLED/messaging.kafka.enabled) 잔여 grep 0.
TODO
- 두 번째 broker adapter 추가 시 중앙
MessagingConfig무변경을 검증한다 — 등급:needs-confirmation - 정식 CA 리뷰 체인을 실행하고 결과를 기록한다 — 등급:
needs-confirmation
진행 중 메모
- 현 구현 evidence와 잔여 검증 항목은 위
## 검증 / Verification및## 검증해야 할 주장 / Claims To Verify가 소유한다.
결정 사항
- branch-local 결정의 정본은
## Decision Evidence Map / 결정-근거 매핑D1~D7이다. - project-level 상속 결정은
## Branch Contract Packet이 소유한다.
구현 가이드
MessageBrokerSPI와 포트별 fail-open/fail-closed decorator 경계를 유지한다.- broker adapter는 자기 설정과 조건부 등록을 소유하고 core transport contract를 참조하지 않는다.
app.messaging.broker값에 따라 단일 broker를 선택하며 disabled 구현은 포트별 bean으로 유지한다.- 전체 test와 environment-key 검증으로 routing·fallback·legacy key 제거를 확인한다.
엣지·실패·의존
- 실패 모드: 한 bean이 두 outbound port를 동시에 구현하면 type 조회가 모호해질 수 있다. 포트별 disabled bean으로 차단한다.
- 의존: raw/branch-notes/feature-domain-event-outbox-contract의 fail-closed outbox contract를 보존한다.
- 경계: per-topic 다중 broker routing은 현재 단일 활성 broker contract 밖이다.
마주친 문제
- 통합
DisabledMessaging이NoUniqueBeanDefinitionException을 일으켜 포트별 구현으로 분리했다. 재현과 해결 evidence는## 검증 / Verification에 기록돼 있다.
관련 일일 노트
- 연결된 일일 노트 없음.
완료 후 정리
- 현재 구현·로컬 검증은 완료됐으나 두 번째 broker 확장 검증과 정식 리뷰 체인은 남아 있다.
묶음 (파생 raw 문서)
- raw/errors/: 후보 1건(보류) — "한 클래스가 두 Spring 포트를 구현하면
getBean(Type)이 NoUniqueBeanDefinitionException; 데코레이터/센티넬은 포트별 1클래스로 분리". 재발 가능 패턴이라 errors 노트화 가치 있음(실행 라운드 후). - raw/interviews/: 후보 1건(보류) — "확장 가능한 어댑터 추상화: 포트만으로 부족하고 선택/와이어링 계층(SPI+라우터+데코레이터)까지 설계해야 '파일만 추가' 확장이 된다".
- raw/blog-topics/: 후보 2건(보류):
- "cache 멀티백엔드 라우터 패턴을 메시징(outbox 포함)에 이식 — Kafka-binary 플래그에서 backend-neutral SPI 로".
- "fail-open vs fail-closed 를 바인딩 레벨 데코레이터로 분리해 한 SPI 로 두 실패 계약 보존하기".