--- kind: CONCEPT slug: messaging-spring-cloud-stream-bridge-c03 title: 보장에 의존하는 순간 브리지를 거절한다 topic: admission-budget-and-backpressure project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 rootTreeNode: concept:messaging-spring-cloud-stream-bridge-c03 evidenceCapturedOn: 2026-09-01 assets: - key: messaging-spring-cloud-stream-bridge-c03 file: ../../../final/evidence/rendered/messaging-spring-cloud-stream-bridge-c03.svg - key: messaging-spring-cloud-stream-bridge-c03-diagram file: ../../../final/assets/diagrams/messaging-spring-cloud-stream-bridge-c03.svg evidence: - ../../../final/evidence/raw/messaging-spring-cloud-stream-bridge-c03.txt source: - 원본 분석 절은 analysis/messaging/messaging-spring-cloud-stream-bridge.md#L146 이다. module: messaging-spring-cloud-stream-bridge --- # 보장에 의존하는 순간 브리지를 거절한다 브리지는 상호운용을 위해 존재하고 그 위험은 구체적이다 — Stream이 자기 binder 설정을 소유하므로 목적지 프로파일이 모르는 직렬화기·오류 처리·확인 모드를 바인딩이 조용히 얻을 수 있다. 그래서 플랫폼의 보장에 의존하지 않는 목적지만 허용한다. ## 관계 - **허용 의존 목록은 상한이므로 미사용을 잡지 않는다** 같은 분석 리프에서 끌어낸 규칙이다. - **등록을 받는 컴포넌트는 해제도 제공한다** 같은 분석 리프에서 끌어낸 규칙이다. - **함께 읽히는 두 맵은 한 값으로 묶는다** 같은 분석 리프에서 끌어낸 규칙이다. - **에러 메시지가 지시하는 설정은 그 설정을 읽는 코드와 함께 존재해야 한다** 같은 분석 리프에서 끌어낸 규칙이다. - **한 개념의 등록 상태를 두 객체가 나눠 갖지 않는다** 같은 분석 리프에서 끌어낸 규칙이다. ## 본문 브리지의 위험이 무엇인지 javadoc이 먼저 적는다. > "The bridge exists for interoperability with existing Spring Cloud Stream bindings, and its risk is specific: Stream owns its own binder configuration, so a binding can quietly acquire its own serializer, its own error handling, and its own acknowledgement mode — none of which the destination profile knows about." **세 거절이 `DestinationProfile`의 세 필드를 직접 본다.** | 조건 | 코드 | |---|---| | `profile.isOrdered()` — `orderingScope != NONE` | `STREAM_BRIDGE_ORDERING_UNSUPPORTED` | | `profile.retry().mode() != RetryMode.NONE` | `STREAM_BRIDGE_RETRY_UNSUPPORTED` | | `profile.deadLetter().enabled()` | `STREAM_BRIDGE_DLQ_UNSUPPORTED` | ## 브리지가 허용되는 범위 :::evidence key="messaging-spring-cloud-stream-bridge-c03-diagram" alt="가드 경계 안에 순서 없음과 재시도 없음과 DLQ 없음 세 조건이 들어 있고 production 목적지가 경계 밖 점선 상자로 놓인 구조" caption="브리지가 허용되는 범위" zoom="false" ::: 세 코드 전부 `MessagingConfigurationException`이고 안정 코드를 갖는다 — `messaging-kafka-share-experimental`이 두 거절에 다른 예외 타입을 쓴 것(그쪽 §17)과 대비된다. `!enabled`도 같은 예외 타입이다. 에러 메시지가 **두 선택지를 명시한다** — "remove it from the binding or move the destination to the native adapter". 무엇을 하라고만 하지 않고 어느 쪽을 포기할지를 준다. ## DestinationProfile 참조 위치 :::evidence key="messaging-spring-cloud-stream-bridge-c03" alt="코드베이스에서 DestinationProfile 를 검색한 출력 8줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="DestinationProfile 코드베이스 검색 — 8줄 · exit 0" zoom="true" ::: ## 네 번째 게이트 **production 목적지는 무조건 거절한다.** guard의 세 조건을 통과한 목적지(순서 없음·재시도 없음·DLQ 없음)라도 production이면 막는다. 바인딩 이름 패턴 `[a-zA-Z][a-zA-Z0-9-]{0,63}` — 언더스코어와 점을 배제한다. ## gaps()가 플래그가 아니라 문장을 만드는 이유 **"nothing at runtime will show it"**이 이 record가 존재하는 이유다. 네 boolean과 두 factory: `gaps()`가 각 `false`마다 **문장 하나**를 만든다. **각 문장이 결과까지 적는다** — "indistinguishable", "loses the message". 상태 플래그가 아니라 운영자가 읽는 진술이다. `isFullyGuaranteed()`가 `gaps().isEmpty()`다 — 매 호출마다 네 문장을 다시 만든다. 성능 문제는 아니지만 순수 조회가 문자열을 할당한다.