Files
llm-wiki/raw/branch-notes/feature-messaging-multibroker-router.md

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
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-EVENT-BROKER-001@1
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1
WI-CA-SKELETON-OPERATIONAL-CONTRACT-038
1 feature-messaging-multibroker-router
ca-skeleton
branch
ca-skeleton
adapter-outbound
messaging
kafka
spi
extensibility
refactoring
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_ENABLEDapp.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이 소유한다.

구현 가이드

  1. MessageBroker SPI와 포트별 fail-open/fail-closed decorator 경계를 유지한다.
  2. broker adapter는 자기 설정과 조건부 등록을 소유하고 core transport contract를 참조하지 않는다.
  3. app.messaging.broker 값에 따라 단일 broker를 선택하며 disabled 구현은 포트별 bean으로 유지한다.
  4. 전체 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 밖이다.

마주친 문제

  • 통합 DisabledMessagingNoUniqueBeanDefinitionException을 일으켜 포트별 구현으로 분리했다. 재현과 해결 evidence는 ## 검증 / Verification에 기록돼 있다.

관련 일일 노트

  • 연결된 일일 노트 없음.

완료 후 정리

  • 현재 구현·로컬 검증은 완료됐으나 두 번째 broker 확장 검증과 정식 리뷰 체인은 남아 있다.

묶음 (파생 raw 문서)

  • raw/errors/: 후보 1건(보류) — "한 클래스가 두 Spring 포트를 구현하면 getBean(Type) 이 NoUniqueBeanDefinitionException; 데코레이터/센티넬은 포트별 1클래스로 분리". 재발 가능 패턴이라 errors 노트화 가치 있음(실행 라운드 후).
  • raw/interviews/: 후보 1건(보류) — "확장 가능한 어댑터 추상화: 포트만으로 부족하고 선택/와이어링 계층(SPI+라우터+데코레이터)까지 설계해야 '파일만 추가' 확장이 된다".
  • raw/blog-topics/: 후보 2건(보류):
    1. "cache 멀티백엔드 라우터 패턴을 메시징(outbox 포함)에 이식 — Kafka-binary 플래그에서 backend-neutral SPI 로".
    2. "fail-open vs fail-closed 를 바인딩 레벨 데코레이터로 분리해 한 SPI 로 두 실패 계약 보존하기".