Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/admission-budget-and-backpressure/concept/concept-messaging-spring-cloud-stream-bridge-c03.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

77 lines
4.6 KiB
Markdown

---
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 설정을 소유하므로 목적지 프로파일이 모르는 직렬화기·오류 처리·확인 모드를 바인딩이 조용히 얻을 수 있다. 그래서 플랫폼의 보장에 의존하지 않는 목적지만 허용한다.
## 관계
- **허용 의존 목록은 상한이므로 미사용을 잡지 않는다**
같은 분석 리프에서 끌어낸 규칙이다.
- **등록을 받는 컴포넌트는 해제도 제공한다**
같은 분석 리프에서 끌어낸 규칙이다.
- **함께 읽히는 두 맵은 한 값으로 묶는다**
같은 분석 리프에서 끌어낸 규칙이다.
- **에러 메시지가 지시하는 설정은 그 설정을 읽는 코드와 함께 존재해야 한다**
같은 분석 리프에서 끌어낸 규칙이다.
- **한 개념의 등록 상태를 두 객체가 나눠 갖지 않는다**
같은 분석 리프에서 끌어낸 규칙이다.
## 본문
<!-- body:start -->
브리지의 위험이 무엇인지 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()`다 — 매 호출마다 네 문장을 다시 만든다. 성능 문제는 아니지만 순수 조회가 문자열을 할당한다.
<!-- body:end -->