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>
100 lines
6.1 KiB
Plaintext
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개 테스트를 변경 없이 통과한다. 결정적 하네스를 쓰므로
|