Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/a19-f014-kafka-msg.txt
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

141 lines
11 KiB
Plaintext

# 가족 문서가 이 항목을 어떻게 들고 있는가
messaging/CLAUDE.md:70:## application이 이 family에 도달하는 경로 (MSG-015, 미해결)
messaging/CLAUDE.md:82:이것이 리뷰의 **MSG-015 (P0)** 이며 닫히지 않았다. 지금 안전한 이유는 설계가 아니라 기본값이다 —
# 두 스택의 설정 클래스와 생산자 빈
bootstrap/messaging/KafkaSenderConfig.java:37 @Configuration(proxyBeanMethods = false)
bootstrap/messaging/KafkaSenderConfig.java:38 @ConditionalOnProperty(name = "app.messaging.broker", havingValue = "kafka")
bootstrap/messaging/KafkaSenderConfig.java:39 public class KafkaSenderConfig {
bootstrap/messaging/KafkaSenderConfig.java:61 @Bean(name = "kafkaSeamProducer", destroyMethod = "close")
bootstrap/messaging/KafkaSenderConfig.java:62 @ConditionalOnMissingBean(name = "kafkaSeamProducer")
bootstrap/messaging/KafkaSenderConfig.java:63 public Producer<String, String> kafkaSeamProducer(KafkaAdapterSettings settings) {
autoconfigure/KafkaMessagingAutoConfiguration.java:126 * @return the producer, closed with the context
autoconfigure/KafkaMessagingAutoConfiguration.java:127 */
autoconfigure/KafkaMessagingAutoConfiguration.java:128 @Bean(destroyMethod = "close")
autoconfigure/KafkaMessagingAutoConfiguration.java:129 @ConditionalOnMissingBean
autoconfigure/KafkaMessagingAutoConfiguration.java:130 public org.apache.kafka.clients.producer.Producer<byte[], byte[]> messagingKafkaProducer(
autoconfigure/KafkaMessagingAutoConfiguration.java:131 org.springframework.core.env.Environment environment) {
# 플랫폼 쪽이 켜지는 사슬
main · messaging/autoconfigure/MessagingPlatformRootAutoConfiguration.java:28 @ConditionalOnProperty(prefix = MessagingSettings.PREFIX, name = "enabled", havingValue = "true")
main · messaging/autoconfigure/MessagingProviderSelection.java:48 "kafka", KafkaMessagingAutoConfiguration.class,
# broker 만 준 시험이 통과하는 이유 — 게이트 뒤 설정을 손으로 등록한다
test · adapter/outbound/messaging/MessagingConfigTest.java:41 .withUserConfiguration(KafkaAdapterConfig.class)
test · OptionalAdapterBeanGatingTest.java:59 private final ApplicationContextRunner runner =
test · OptionalAdapterBeanGatingTest.java:60 new ApplicationContextRunner()
test · OptionalAdapterBeanGatingTest.java:61 // Registered as a real auto-configuration entry, which is how the composition root
test · OptionalAdapterBeanGatingTest.java:62 // reaches it: the HTTP Client platform's off state is a property of that entry, not of a
test · OptionalAdapterBeanGatingTest.java:63 // hand-assembled subset of its children.
test · OptionalAdapterBeanGatingTest.java:64 .withConfiguration(AutoConfigurations.of(HttpClientPlatformAutoConfiguration.class))
test · OptionalAdapterBeanGatingTest.java:65 .withUserConfiguration(
test · OptionalAdapterBeanGatingTest.java:66 OutboundSupportConfig.class,
test · OptionalAdapterBeanGatingTest.java:67 MessagingConfig.class,
test · OptionalAdapterBeanGatingTest.java:68 KafkaAdapterConfig.class,
test · OptionalAdapterBeanGatingTest.java:69 NotificationConfig.class,
test · OptionalAdapterBeanGatingTest.java:70 SlackNotificationAdapterConfig.class,
두 시험이 컴포지션 루트를 쓰는가 :
MessagingConfigTest.java CaSkeletonApplication 언급 0 건
OptionalAdapterBeanGatingTest.java CaSkeletonApplication 언급 0 건
KafkaSenderConfig 가 그 슬라이스에 등록되는가 :
MessagingConfigTest.java KafkaSenderConfig 언급 0 건
OptionalAdapterBeanGatingTest.java KafkaSenderConfig 언급 0 건
# 이 조합을 거르는 검증기가 있는가
CapabilityDependencyValidator.java:64 }
CapabilityDependencyValidator.java:65 if (relay && !outbox) {
CapabilityDependencyValidator.java:66 violations.add(
CapabilityDependencyValidator.java:67 "ca-skeleton.outbox.relay-enabled=true only starts the scheduler for a capability that is "
CapabilityDependencyValidator.java:68 + "off; set ca-skeleton.outbox.enabled=true or turn the relay off.");
CapabilityDependencyValidator.java:69 }
CapabilityDependencyValidator.java:70 if (messaging && broker.isEmpty()) {
CapabilityDependencyValidator.java:71 violations.add(
CapabilityDependencyValidator.java:72 "app.messaging.enabled=true needs a transport; set app.messaging.broker to a registered "
CapabilityDependencyValidator.java:73 + "broker id.");
CapabilityDependencyValidator.java:74 }
CapabilityDependencyValidator.java:75 if ("jdbc"
CapabilityDependencyValidator.java:76 .equalsIgnoreCase(
CapabilityDependencyValidator.java:77 environment.getProperty(
CapabilityDependencyValidator.java:78 "ca-skeleton.capabilities.idempotency.provider", "disabled"))
CapabilityDependencyValidator.java:79 && !jpa) {
CapabilityDependencyValidator.java:80 violations.add(
# 운영 문서가 이 키를 무엇이라고 설명하는가
docs/runbooks/outbox-publish-failed.md:26 ### Step 1 — 확인
docs/runbooks/outbox-publish-failed.md:27 1. ERROR log에서 `OUTBOX_PUBLISH_FAILED` 라인 확인: `event_type`, `event_id`, `correlation_id`, `attempt_count` 추출
docs/runbooks/outbox-publish-failed.md:28 2. broker 상태 확인: `APP_MESSAGING_BROKER` 값과 broker endpoint 가용성. 이 키는 활성화 스위치가
docs/runbooks/outbox-publish-failed.md:29 아니라 **선택자**다 — messaging을 끄는 것은 `APP_MESSAGING_ENABLED=false`이고, 이 값을 비운다고
docs/runbooks/outbox-publish-failed.md:30 messaging이 꺼지지는 않는다.
docs/runbooks/outbox-publish-failed.md:31 - `APP_MESSAGING_BROKER`가 공백인 채로 relay가 켜져 있으면 **애플리케이션이 기동하지 않는다**
docs/runbooks/outbox-publish-failed.md:32 (`OutboxRelayBrokerRequirementValidator`, MSG-024). 이 조합에서는 publish가 전부
# 출하 설정이 두 키를 어떻게 주는가
compose-profile-contracts.json:170: "APP_MESSAGING_ENABLED": "true",
compose-profile-contracts.json:171: "APP_MESSAGING_BROKER": "kafka",
compose-profile-contracts.json:202: "APP_MESSAGING_ENABLED": "true",
compose-profile-contracts.json:203: "APP_MESSAGING_BROKER": "kafka",
compose-profile-contracts.json:604: "APP_MESSAGING_ENABLED": "true",
compose-profile-contracts.json:605: "APP_MESSAGING_BROKER": "kafka",
.env:38:APP_MESSAGING_BROKER=
.env.example:229:APP_MESSAGING_BROKER=
.env.example:299:APP_MESSAGING_ENABLED=false
.env.local.example:20:APP_MESSAGING_ENABLED=false
.env.local.example:21:APP_MESSAGING_BROKER=
# KafkaSenderConfig 가 조건에서 물러나는가
KafkaSenderConfig.java:60 */
KafkaSenderConfig.java:61 @Bean(name = "kafkaSeamProducer", destroyMethod = "close")
KafkaSenderConfig.java:62 @ConditionalOnMissingBean(name = "kafkaSeamProducer")
KafkaSenderConfig.java:63 public Producer<String, String> kafkaSeamProducer(KafkaAdapterSettings settings) {
KafkaSenderConfig.java:64 Map<String, Object> config = new HashMap<>();
KafkaSenderConfig.java:65 config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, String.join(",", settings.brokers()));
# 이음매 설정 클래스 자신은 스캔 대상인가
KafkaSenderConfig.java:1 package dev.caskeleton.bootstrap.messaging;
AUTO_CONFIGURED_PACKAGES 가 bootstrap.messaging 을 제외하는가 : 0 건
# 이음매 쪽 생산자가 요구하는 설정 빈은 어디서 오는가
main · adapter/outbound/messaging/kafka/KafkaAdapterConfig.java:19 @EnableConfigurationProperties(KafkaAdapterSettings.class)
main · adapter/outbound/messaging/autoconfigure/MessagingBridgeRootAutoConfiguration.java:21 @ConditionalOnProperty(prefix = "app.messaging", name = "enabled", havingValue = "true")
main · adapter/outbound/messaging/autoconfigure/MessagingBridgeRootAutoConfiguration.java:23 @Import({MessagingConfig.class, KafkaAdapterConfig.class})
# 그 설정 클래스가 컴포넌트 스캔으로 들어올 수 있는가
CaSkeletonApplication.java:41 @ComponentScan(
CaSkeletonApplication.java:42 basePackages = {
CaSkeletonApplication.java:43 "dev.caskeleton.bootstrap",
CaSkeletonApplication.java:44 "dev.caskeleton.adapter",
CaSkeletonApplication.java:45 "dev.caskeleton.application",
CaSkeletonApplication.java:46 "dev.caskeleton.domain",
CaSkeletonApplication.java:47 "dev.caskeleton.shared"
CaSkeletonApplication.java:48 },
CaSkeletonApplication.java:49 excludeFilters = {
CaSkeletonApplication.java:50 @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
CaSkeletonApplication.java:51 @ComponentScan.Filter(
CaSkeletonApplication.java:52 type = FilterType.CUSTOM,
CaSkeletonApplication.java:53 classes = AutoConfigurationExcludeFilter.class),
CaSkeletonApplication.java:54 @ComponentScan.Filter(
CaSkeletonApplication.java:55 type = FilterType.REGEX,
CaSkeletonApplication.java:56 pattern = CaSkeletonApplication.AUTO_CONFIGURED_PACKAGES)
CaSkeletonApplication.java:57 })
CaSkeletonApplication.java:100 static final String AUTO_CONFIGURED_PACKAGES =
CaSkeletonApplication.java:101 "dev\\.caskeleton\\.bootstrap\\.autoconfigure\\..*"
CaSkeletonApplication.java:105 + "|dev\\.caskeleton\\.adapter\\.outbound\\.messaging\\..*"
ConfigurationPropertiesScan 목록에 adapter.outbound.messaging 이 있는가 :
0 건
# broker=kafka 를 쓰는 시험마다 같은 파일이 enabled 를 주는가
MessagingConfigTest.java broker=kafka 3 줄 · enabled= 0 줄
OptionalAdapterBeanGatingTest.java broker=kafka 1 줄 · enabled= 0 줄
MessagingLiveRoundTripQualificationTest.java broker=kafka 1 줄 · enabled= 1 줄
MessagingStarterOffContractTest.java broker=kafka 3 줄 · enabled= 7 줄
enabled 를 한 줄도 주지 않는 파일의 시험 이름 :
MessagingConfigTest.java:25 void blankBrokerBindsDisabledSentinelsCharacterization() {
MessagingConfigTest.java:39 void selectedKafkaBrokerWithoutProjectSenderFailsStartupCharacterization() {
MessagingConfigTest.java:54 void selectedBrokerIdMismatchFailsStartupCharacterization() {
MessagingConfigTest.java:76 public void send(
OptionalAdapterBeanGatingTest.java:77 void allOptionalAdaptersAreDisabledByDefaultAndTheAppStillStarts() {
OptionalAdapterBeanGatingTest.java:114 void kafkaSelectedRegistersTheBrokerAndBindsTheRealPublishers() {
OptionalAdapterBeanGatingTest.java:135 void slackWebhookEnabledContributesTheProviderAndRoutesBoundNotifications() {
OptionalAdapterBeanGatingTest.java:153 void googleEmailEnabledContributesTheProviderAndRoutesBoundNotifications() {
OptionalAdapterBeanGatingTest.java:170 void aRouteBindingToADisabledProviderFailsStartup() {