# evidence 257 — messaging-msg015-two-kafka-stacks # revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5 # cwd: /shared/codebase/clean-architecture-backend-template/src/messaging # command: echo ---app-bootstrap seam---; sed -n "36,40p" ../app-bootstrap/src/main/java/dev/caskeleton/bootstrap/messaging/KafkaSenderConfig.java; sed -n "58,66p" ../app-bootstrap/src/main/java/dev/caskeleton/bootstrap/messaging/KafkaSenderConfig.java; echo; echo ---platform producer---; sed -n "124,136p" messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java; echo; echo ---root master switch---; grep -n "ConditionalOnProperty" messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/MessagingPlatformRootAutoConfiguration.java; echo; echo ---CLAUDE.md MSG-015---; grep -n -B2 -A10 "MSG-015" CLAUDE.md # ---- raw output ---- ---app-bootstrap seam--- */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "app.messaging.broker", havingValue = "kafka") public class KafkaSenderConfig { * @param settings the broker addresses, already validated as non-empty when Kafka is selected * @return the producer, closed on context shutdown */ @Bean(name = "kafkaSeamProducer", destroyMethod = "close") @ConditionalOnMissingBean(name = "kafkaSeamProducer") public Producer kafkaSeamProducer(KafkaAdapterSettings settings) { Map config = new HashMap<>(); config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, String.join(",", settings.brokers())); config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); ---platform producer--- * * @param environment the resolved environment * @return the producer, closed with the context */ @Bean(destroyMethod = "close") @ConditionalOnMissingBean public org.apache.kafka.clients.producer.Producer messagingKafkaProducer( org.springframework.core.env.Environment environment) { String bootstrapServers = environment.getProperty("spring.kafka.bootstrap-servers", "").trim(); if (bootstrapServers.isEmpty()) { throw new IllegalStateException( "app.messaging.broker=kafka needs spring.kafka.bootstrap-servers; a producer with no " + "broker address defaults to localhost:9092 and fails at the first publish rather " ---root master switch--- 4:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 28:@ConditionalOnProperty(prefix = MessagingSettings.PREFIX, name = "enabled", havingValue = "true") ---CLAUDE.md MSG-015--- 68- 증명되었는가", membership은 "무엇이 실행되는가"다. 69- 70:## application이 이 family에 도달하는 경로 (MSG-015, 미해결) 71- 72-리뷰(`docs/reviews/2026-08-14-messaging-module-code-review.md` §6.1·§6.3)가 정한 흐름은 73-`application-owned port → platform anti-corruption bridge → platform`이고, §6.2는 그 bridge의 자리를 74-`adapter/outbound/messaging/platformbridge/`로 지정한다. §6.3 첫 규칙: **application은 신규 75-broker/runtime 타입을 직접 보지 않고 자기 port만 소유한다.** 76- 77-**현재 그 bridge가 없다.** `adapter/outbound/messaging`은 platform에 의존하지 않고(registry의 78-`allowed_dependencies` 참조), `app-bootstrap`이 starter를 직접 문다. 그래서 서로를 모르는 messaging 79-스택 두 개가 한 아티팩트에 있고, 그것이 `kafkaSeamProducer`/`messagingKafkaProducer` bean 이름 충돌로 80-드러났다. 81- 82:이것이 리뷰의 **MSG-015 (P0)** 이며 닫히지 않았다. 지금 안전한 이유는 설계가 아니라 기본값이다 — 83-`app.messaging.enabled=false`. 이 스위치를 켜는 배포는 port를 거치지 않는 경로로 platform을 쓰게 된다. 84-bridge 구현은 런타임 배선 변경이라 리뷰가 말한 대로 별도 change set이다. 85- 86-## Stable 승격에 필요한 증거 87- 88-adapter를 Experimental → Stable로 올릴 때 필요한 것 (`docs/messaging/support-matrix.md`가 현재 89-상태를 기록한다): 90- 91-1. 실제 broker container를 쓰는 fault 시나리오 lane이 존재하고, 그 lane이 92- `BrokerCertificationEvidence`를 산출한다.