# evidence 256 — messaging-provider-selection-and-rabbit-transport # revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5 # cwd: /shared/codebase/clean-architecture-backend-template/src/messaging # command: sed -n "1,80p" messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/MessagingProviderSelection.java; echo; echo ---RabbitChannelPublisher implementations---; grep -rn "implements RabbitChannelPublisher" --include=*.java .; echo "(no matches == no implementation anywhere)"; echo; echo ---MessagingTransport beans in the starter---; grep -rn "MessagingTransport" messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/RabbitMessagingAutoConfiguration.java messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java; echo; echo ---support-matrix.md Rabbit row---; grep -n "RabbitMQ" ../../docs/messaging/support-matrix.md | head -5 # ---- raw output ---- package dev.caskeleton.messaging.autoconfigure; import java.util.List; import java.util.Map; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.ClassUtils; /** * Selects exactly one transport, from a closed registry, by name (MSG-INT-004). * *

Selection used to be {@code @ConditionalOnClass}: Kafka assembled because a Kafka class was on * the classpath and Rabbit because a Rabbit class was, so an application with both libraries — one * transitive dependency is enough — assembled both and published through whichever bean won a * {@code @ConditionalOnMissingBean} race. Nothing failed; the message simply went somewhere nobody * chose. * *

Three outcomes are startup errors now rather than silences: * *

* *

The provider configurations carry no condition of their own any more. Each is reached only * along the path this class chose, which is what makes "which transport is active" answerable by * reading one property rather than by resolving a dependency graph. */ @Configuration(proxyBeanMethods = false) @Import(MessagingProviderSelection.Selector.class) public class MessagingProviderSelection { /** Broker id to the client class that must be present for it to be honoured. */ static final Map REGISTERED_BROKERS = Map.of( "kafka", "org.apache.kafka.clients.producer.Producer", "rabbit", "com.rabbitmq.client.Channel"); /** Broker id to the configuration that assembles it. */ static final Map> PROVIDER_CONFIGURATIONS = Map.of( "kafka", KafkaMessagingAutoConfiguration.class, "rabbit", RabbitMessagingAutoConfiguration.class); /** * Broker ids that ship without a {@code MessagingTransport}, and why. * *

Registered is not the same as assemblable. The Rabbit leaf ships its profile validator, * failure classifier and security configurer, and no transport — the native channel bridge those * would sit on has only test implementations. Selecting it therefore produced an unsatisfied * dependency on {@code MessagingTransport} deep inside the core configuration, which tells an * operator that a bean is missing rather than that the transport they chose is not finished. * *

Refused here instead, next to the other selection errors, so the message names the state the * deployment is actually in. An entry leaves this map on the day its transport does exist. */ static final Map BROKERS_WITHOUT_A_TRANSPORT = Map.of( "rabbit", "the Rabbit adapter ships its validators and security configuration but no " + "MessagingTransport: its native channel publisher is not implemented, so a publish " + "has nothing to travel on"); /** The registered broker ids, so a message can tell an operator what is available. */ public static List registeredBrokerIds() { return REGISTERED_BROKERS.keySet().stream().sorted().toList(); } /** * The registered broker ids that actually assemble a transport. * * @return the ids a deployment can select today */ public static List assemblableBrokerIds() { ---RabbitChannelPublisher implementations--- (no matches == no implementation anywhere) ---MessagingTransport beans in the starter--- messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:163: @ConditionalOnMissingBean(dev.caskeleton.messaging.transport.MessagingTransport.class) messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:164: public dev.caskeleton.messaging.kafka.KafkaMessagingTransport messagingKafkaTransport( messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:168: return new dev.caskeleton.messaging.kafka.KafkaMessagingTransport("kafka", 1L, producer); ---support-matrix.md Rabbit row--- 7:> boolean이 아니라 `CertifiedEvidence`가 가진 레인 증거에서 파생된다. RabbitMQ가 Stable에서 내려온 34:| RabbitMQ | Experimental | 4.3.x | exchange/routing, publisher confirm, mandatory return, manual ACK, quorum queue, retry queue, DLQ | 장애 시나리오 레인 미실행 — 증거 없음. stream 및 특수 plugin 미지원 | 44:| Capability | Kafka | Kafka Share | RabbitMQ | Pulsar | NATS JS | 86:Kafka와 RabbitMQ가 동일한 7개 테스트를 변경 없이 통과한다. 결정적 하네스를 쓰므로