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>
93 lines
6.1 KiB
Plaintext
93 lines
6.1 KiB
Plaintext
# 주제: messaging-kafka 34개 main 타입 중 무엇이 조립되고 무엇이 조립되지 않는가
|
|
# revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
|
|
# command: git grep -nE "new ([a-zA-Z0-9_.]+\.)?<T>\s*\(" -- src (T별 집계)
|
|
KafkaMessagingTransport src/main=1 src/test+jmh=12 <- 스타터가 3-arg 로 생성
|
|
KafkaSecurityConfigurer src/main=1 src/test+jmh=2
|
|
KafkaProfileValidator src/main=1 src/test+jmh=1
|
|
KafkaTransactionProfileValidator src/main=1 src/test+jmh=0
|
|
KafkaPublishFailureClassifier src/main=2 src/test+jmh=1
|
|
KafkaPublishMapper src/main=2 src/test+jmh=0 (transport/transactional 내부)
|
|
KafkaHeaderMapper src/main=2 src/test+jmh=4 (두 매퍼 내부)
|
|
KafkaTransactionalPublisher src/main=1 src/test+jmh=1 (자기 정적 팩토리)
|
|
KafkaDeliveryMapper src/main=1 src/test+jmh=1 (registrar 내부)
|
|
KafkaSettlementQueue src/main=1 src/test+jmh=0 (registrar 내부)
|
|
KafkaPartitionRetryScheduler src/main=1 src/test+jmh=0 (registrar 내부)
|
|
PartitionWorkCoordinator src/main=1 src/test+jmh=6 (registrar 내부)
|
|
ContiguousPartitionOffsetTracker src/main=1 src/test+jmh=2 (registrar 내부)
|
|
KafkaRetryMetadataMapper src/main=2 src/test+jmh=3
|
|
-----
|
|
KafkaConsumerRegistrar src/main=0 src/test+jmh=6 <- 테스트에서만
|
|
KafkaOffsetResetExecutor src/main=0 src/test+jmh=2 <- 테스트에서만
|
|
KafkaReplayPlanner src/main=0 src/test+jmh=1 <- 테스트에서만
|
|
SpringKafkaTransactionalProcessor src/main=0 src/test+jmh=1 <- 테스트에서만
|
|
-----
|
|
KafkaBatchConsumerRegistrar src/main=0 src/test+jmh=0 <- **어디에서도 생성되지 않음**
|
|
KafkaRetryExecutor src/main=0 src/test+jmh=0 <- **어디에서도**
|
|
KafkaRetryTopicPublisher src/main=0 src/test+jmh=0 <- **어디에서도**
|
|
KafkaDeadLetterPublisher src/main=0 src/test+jmh=0 <- **어디에서도**
|
|
KafkaReplayCapability src/main=0 src/test+jmh=0 <- **어디에서도**
|
|
KafkaTopologyInspector src/main=0 src/test+jmh=0 <- 인터페이스, 구현 0
|
|
|
|
# src/main 에서 이 leaf 의 타입을 만드는 외부 지점 (전수)
|
|
KafkaMessagingAutoConfiguration.java:42 new KafkaProfileValidator()
|
|
KafkaMessagingAutoConfiguration.java:75 new KafkaTransactionProfileValidator()
|
|
KafkaMessagingAutoConfiguration.java:86 new KafkaPublishFailureClassifier()
|
|
KafkaMessagingAutoConfiguration.java:111 new KafkaSecurityConfigurer(credentials, tlsPolicy)
|
|
KafkaMessagingAutoConfiguration.java:168 new KafkaMessagingTransport("kafka", 1L, producer)
|
|
MessagingConfigurationCompiler.java:116 new KafkaBrokerProfile(...)
|
|
# => 외부에서 생성되는 것은 검증기 3 + 보안 설정기 1 + 전송 1 + 프로필 1 = 6개뿐이다.
|
|
|
|
# ---- 소비 불가가 타입으로 표현되어 있다 ----
|
|
# KafkaMessagingTransport.java:74-92 (스타터가 쓰는 3-arg 생성자)
|
|
# public KafkaMessagingTransport(String brokerName, long generation, Producer<byte[],byte[]> producer) {
|
|
# this(brokerName, generation, producer,
|
|
# spec -> {
|
|
# throw new MessagingCapabilityUnavailableException(
|
|
# "KAFKA_CONSUMER_NOT_CONFIGURED",
|
|
# "this Kafka transport was created without a consumer factory");
|
|
# });
|
|
# }
|
|
# KafkaMessagingTransport.java:159-167
|
|
# public TransportConsumerRegistration register(TransportConsumerSpec spec) {
|
|
# ...
|
|
# return consumerFactory.apply(spec); // 3-arg 경로에서는 항상 throw
|
|
# }
|
|
# => 부팅된 애플리케이션에서 transport.register(...) 는 KAFKA_CONSUMER_NOT_CONFIGURED 로 실패한다.
|
|
# 조용히 아무 일도 안 하는 것이 아니라 이름 붙은 오류로 거절한다.
|
|
# "소비 경로가 배선되지 않았다"(EVD-316)가 이 leaf 에서 타입으로 표현된 형태다.
|
|
|
|
# ---- 토폴로지 인스펙터: 두 인터페이스, 구현 0 ----
|
|
# command: git grep -n "BrokerTopologyInspector" -- src
|
|
# admin-runtime/BrokerTopologyInspector.java:14 (선언)
|
|
# CompositeTopologyValidator:21,29 / DefaultMessagingAdminService:54,80 (소비)
|
|
# MessagingAdminAutoConfiguration:5,81,83 (@ConditionalOnBean)
|
|
# TopologyValidatorTest.java:133 new BrokerTopologyInspector() { ... } <- 유일한 구현, 테스트 익명클래스
|
|
# command: git grep -n "KafkaTopologyInspector" -- src
|
|
# messaging-kafka/KafkaTopologyInspector.java:13 (선언) <- 그 외 등장 0
|
|
#
|
|
# 두 인터페이스의 모양이 다르다:
|
|
# BrokerTopologyInspector : Optional<DestinationTopology> describe(String), String topologyVersion()
|
|
# KafkaTopologyInspector : int partitionCount(String), short replicationFactor(String),
|
|
# Map<String,String> configuration(String), List<String> topics()
|
|
#
|
|
# 실제로 브로커 토폴로지를 읽는 코드는 컨테이너 IT 안의 private static 헬퍼다:
|
|
# KafkaTopologyValidationIT.java:42-61
|
|
# private static DestinationTopology describe(String topic) {
|
|
# try (Admin admin = KafkaContainerFixture.admin()) {
|
|
# Map<String, TopicDescription> described = admin.describeTopics(List.of(topic))...;
|
|
# ...
|
|
# return new DestinationTopology(topic, description.partitions().size(), replication, Map.of(), true);
|
|
# } catch (...) { return DestinationTopology.absent(topic); }
|
|
# }
|
|
# => 어느 인터페이스도 구현하지 않는다.
|
|
#
|
|
# 판정: analysis/messaging/messaging-admin-api.md §17 첫 항목("BLOCKING 이면 기동 실패"가 미배선)을
|
|
# 고치려 해도 **줄 인스펙터가 없다.** Kafka leaf 는 그 자리에 다른 모양의 인터페이스를
|
|
# 선언만 해 두었고, 실제로 동작하는 20줄은 테스트의 private 메서드로만 존재한다.
|
|
|
|
# ---- 이 leaf 를 import 하는 외부 파일은 3개뿐 ----
|
|
# messaging-spring-boot-starter/.../CompiledMessagingConfiguration.java
|
|
# messaging-spring-boot-starter/.../KafkaMessagingAutoConfiguration.java
|
|
# messaging-spring-boot-starter/.../MessagingConfigurationCompiler.java
|