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>
67 lines
4.2 KiB
Plaintext
67 lines
4.2 KiB
Plaintext
# 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<String, String> kafkaSeamProducer(KafkaAdapterSettings settings) {
|
|
Map<String, Object> 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<byte[], byte[]> 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`를 산출한다.
|