Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/256-messaging-provider-selection-and-rabbit-transport.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

100 lines
6.1 KiB
Plaintext

# 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).
*
* <p>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.
*
* <p>Three outcomes are startup errors now rather than silences:
*
* <ul>
* <li>a broker id outside the registry — the operator set something the platform cannot honour;
* <li>a registered broker whose client library is absent — a deployment that asked for Kafka and
* shipped without it is told so, instead of starting with no publisher at all;
* <li>a blank broker while messaging is on — the master switch says yes and nothing says what to.
* </ul>
*
* <p>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<String, String> 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<String, Class<?>> PROVIDER_CONFIGURATIONS =
Map.of(
"kafka", KafkaMessagingAutoConfiguration.class,
"rabbit", RabbitMessagingAutoConfiguration.class);
/**
* Broker ids that ship without a {@code MessagingTransport}, and why.
*
* <p>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.
*
* <p>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<String, String> 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<String> 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<String> 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개 테스트를 변경 없이 통과한다. 결정적 하네스를 쓰므로