Files
document-haness/docs/clean-architecture-backend-template/final/evidence/rendered/a06-f018-changestreams-false-wiring.svg
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

161 lines
14 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1734" viewBox="0 0 1200 1734" role="img">
<title>terminal evidence</title>
<desc>Terminal-style rendering generated from retained command output. Sensitive-looking values are redacted in the visual asset.</desc>
<rect x="1" y="1" width="1198" height="1732" rx="14" fill="#0d1117" stroke="#30363d"/>
<rect x="1" y="1" width="1198" height="44" rx="14" fill="#161b22"/>
<rect x="1" y="30" width="1198" height="14" fill="#161b22"/>
<circle cx="24" cy="22" r="6" fill="#ff5f57"/>
<circle cx="44" cy="22" r="6" fill="#febc2e"/>
<circle cx="64" cy="22" r="6" fill="#28c840"/>
<text x="92" y="27" fill="#8b949e" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">terminal evidence</text>
<text x="24" y="68" fill="#c9d1d9" font-size="15" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">$ set -e
D=$(mktemp -d)
trap &#x27;rm -rf &quot;$D&quot;&#x27; EXIT
printf %s &#x27;&lt;configuration&gt;&lt;root level=&quot;OFF&quot;/&gt;&lt;/configuration&gt;&#x27; &gt; &quot;$D/lo</tspan><tspan x="24" dy="22">gback-off.xml&quot;
cat &gt; &quot;$D/ChangeStreamWiringProbe.java&quot; &lt;&lt;&#x27;JAVA&#x27;
import static org.mockito.Mockito.mock;
import com.mon</tspan><tspan x="24" dy="22">godb.client.MongoClients;
import dev.caskeleton.adapter.outbound.mongo.api.consistency.MongoConsistencyRegistry;
import </tspan><tspan x="24" dy="22">dev.caskeleton.adapter.outbound.mongo.api.observation.MongoOperationObserver;
import dev.caskeleton.adapter.outbound.mon</tspan><tspan x="24" dy="22">go.autoconfigure.MongoPlatformAutoConfiguration;
import dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoPlatfor</tspan><tspan x="24" dy="22">mSettings;
import dev.caskeleton.adapter.outbound.mongo.changestream.MongoChangeStreamSubscription;
import dev.caskeleto</tspan><tspan x="24" dy="22">n.adapter.outbound.mongo.changestream.MongoResumeCheckpointStore;
import dev.caskeleton.adapter.outbound.mongo.changestr</tspan><tspan x="24" dy="22">eam.MongoResumeTokenCodec;
import dev.caskeleton.adapter.outbound.mongo.changestream.consumer.MongoChangeStreamSource;
i</tspan><tspan x="24" dy="22">mport dev.caskeleton.adapter.outbound.mongo.changestream.consumer.ReactiveMongoChangeStreamConsumer;
import dev.caskelet</tspan><tspan x="24" dy="22">on.adapter.outbound.mongo.changestream.projector.MongoChangeDeduplicationStore;
import dev.caskeleton.adapter.outbound.m</tspan><tspan x="24" dy="22">ongo.changestream.projector.MongoChangeProjector;
import dev.caskeleton.adapter.outbound.mongo.failure.MongoFailureTrans</tspan><tspan x="24" dy="22">lator;
import dev.caskeleton.adapter.outbound.mongo.imperative.MongoCollectionProfileRegistry;
import org.springframewor</tspan><tspan x="24" dy="22">k.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationConte</tspan><tspan x="24" dy="22">xt;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.data.mongod</tspan><tspan x="24" dy="22">b.core.MongoTemplate;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.dat</tspan><tspan x="24" dy="22">a.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabas</tspan><tspan x="24" dy="22">eFactory;
public final class ChangeStreamWiringProbe {
private static final String ENABLED = &quot;ca-skeleton.persistenc</tspan><tspan x="24" dy="22">e-mongo.enabled=true&quot;;
private static final String FLAG = &quot;ca-skeleton.persistence-mongo.platform.change-streams=true&quot;</tspan><tspan x="24" dy="22">;
/** The whole auto-configuration, not the nested class: the flag lives on the outer one. */
private static final </tspan><tspan x="24" dy="22">ApplicationContextRunner BASE =
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(</tspan><tspan x="24" dy="22">MongoPlatformAutoConfiguration.class))
.withBean(MongoTemplate.class, ChangeStreamWiringProbe::blocking)
</tspan><tspan x="24" dy="22"> .withBean(MongoConsistencyRegistry.class, MongoConsistencyRegistry::standard)
.withBean(
Mo</tspan><tspan x="24" dy="22">ngoCollectionProfileRegistry.class,
() -&gt; MongoCollectionProfileRegistry.builder().register(&quot;events&quot;, &quot;eve</tspan><tspan x="24" dy="22">nts&quot;).build())
.withBean(MongoFailureTranslator.class, () -&gt; mock(MongoFailureTranslator.class))
.wi</tspan><tspan x="24" dy="22">thBean(MongoOperationObserver.class, () -&gt; mock(MongoOperationObserver.class));
public static void main(String[] args</tspan><tspan x="24" dy="22">) {
System.out.println(&quot;[모듈 opt-in 없이]&quot;);
report(&quot;기본 &quot;, BASE.withBean(ReactiveMongoTemplate</tspan><tspan x="24" dy="22">.class,
ChangeStreamWiringProbe::reactive));
System.out.println();
ApplicationContextRunner on = BASE.w</tspan><tspan x="24" dy="22">ithPropertyValues(ENABLED);
System.out.println(&quot;[opt-in, 리액티브 템플릿만]&quot;);
report(&quot;기본 &quot;, on</tspan><tspan x="24" dy="22">.withBean(ReactiveMongoTemplate.class,
ChangeStreamWiringProbe::reactive));
report(&quot;change-streams=true &quot;, o</tspan><tspan x="24" dy="22">n.withBean(ReactiveMongoTemplate.class,
ChangeStreamWiringProbe::reactive).withPropertyValues(FLAG));
System</tspan><tspan x="24" dy="22">.out.println();
System.out.println(&quot;[opt-in, 배포가 공급해야 하는 다섯을 함께]&quot;);
report(&quot;기본 &quot;</tspan><tspan x="24" dy="22">, withFive(on.withBean(ReactiveMongoTemplate.class,
ChangeStreamWiringProbe::reactive)));
report(&quot;change-str</tspan><tspan x="24" dy="22">eams=true &quot;, withFive(on.withBean(ReactiveMongoTemplate.class,
ChangeStreamWiringProbe::reactive)).withPropertyV</tspan><tspan x="24" dy="22">alues(FLAG));
System.out.println();
System.out.println(&quot;[opt-in, 리액티브 템플릿 없이]&quot;);
report(&quot;기본 </tspan><tspan x="24" dy="22"> &quot;, on);
System.exit(0);
}
private static ApplicationContextRunner withFive(ApplicationContextRunner </tspan><tspan x="24" dy="22">runner) {
return runner
.withBean(MongoChangeStreamSubscription.class,
() -&gt; MongoChangeStreamSu</tspan><tspan x="24" dy="22">bscription.of(&quot;probe&quot;, &quot;events&quot;))
.withBean(MongoResumeCheckpointStore.class, () -&gt; mock(MongoResumeCheckpointSt</tspan><tspan x="24" dy="22">ore.class))
.withBean(MongoResumeTokenCodec.class, () -&gt; mock(MongoResumeTokenCodec.class))
.withBean(Mo</tspan><tspan x="24" dy="22">ngoChangeProjector.class, () -&gt; mock(MongoChangeProjector.class))
.withBean(MongoChangeDeduplicationStore.class,</tspan><tspan x="24" dy="22">
() -&gt; mock(MongoChangeDeduplicationStore.class));
}
private static void report(String label, Applicati</tspan><tspan x="24" dy="22">onContextRunner runner) {
runner.run(context -&gt; System.out.println(&quot; &quot; + label
+ &quot; 드라이버쪽=&quot; + present(c</tspan><tspan x="24" dy="22">ontext, MongoChangeStreamSource.class)
+ &quot; 소비자=&quot; + present(context, ReactiveMongoChangeStreamConsumer.class)</tspan><tspan x="24" dy="22">
+ &quot; 설정빈=&quot; + present(context, MongoPlatformSettings.class)
+ flag(context)));
}
private static </tspan><tspan x="24" dy="22">String flag(AssertableApplicationContext context) {
if (context.getBeanNamesForType(MongoPlatformSettings.class).len</tspan><tspan x="24" dy="22">gth == 0) {
return &quot;&quot;;
}
return &quot; changeStreams()=&quot; + context.getBean(MongoPlatformSettings.class).change</tspan><tspan x="24" dy="22">Streams();
}
private static String present(AssertableApplicationContext context, Class&lt;?&gt; type) {
return contex</tspan><tspan x="24" dy="22">t.getBeanNamesForType(type).length &gt; 0 ? &quot;있음&quot; : &quot;없음&quot;;
}
private static MongoTemplate blocking() {
return ne</tspan><tspan x="24" dy="22">w MongoTemplate(
new SimpleMongoClientDatabaseFactory(
MongoClients.create(&quot;mongodb://127.0.0.1:2701</tspan><tspan x="24" dy="22">7&quot;), &quot;wiring-probe&quot;));
}
private static ReactiveMongoTemplate reactive() {
return new ReactiveMongoTemplate(
</tspan><tspan x="24" dy="22"> new SimpleReactiveMongoDatabaseFactory(
com.mongodb.reactivestreams.client.MongoClients.create(&quot;mongod</tspan><tspan x="24" dy="22">b://127.0.0.1:27017&quot;),
&quot;wiring-probe&quot;));
}
}
JAVA
CACHE=/root/.gradle/caches/modules-2/files-2.1
LOCK=src</tspan><tspan x="24" dy="22">/adapter/outbound/persistence-mongo/gradle.lockfile
CP=src/adapter/outbound/persistence-mongo/build/classes/java/main
wh</tspan><tspan x="24" dy="22">ile IFS= read -r line; do
coord=${line%%=*}; confs=${line#*=}
case &quot;,$confs,&quot; in *&quot;,testRuntimeClasspath,&quot;*) ;; *) c</tspan><tspan x="24" dy="22">ontinue ;; esac
g=${coord%%:*}; r=${coord#*:}; n=${r%%:*}; v=${r##*:}
jar=$(find &quot;$CACHE/$g/$n/$v&quot; -name &#x27;*.jar&#x27; ! -</tspan><tspan x="24" dy="22">name &#x27;*sources*&#x27; ! -name &#x27;*javadoc*&#x27; 2&gt;/dev/null | head -1)
[ -n &quot;$jar&quot; ] &amp;&amp; CP=&quot;$CP:$jar&quot;
done &lt; &lt;(grep -E &#x27;^[a-zA-Z0</tspan><tspan x="24" dy="22">-9._-]+:[^=]+=&#x27; &quot;$LOCK&quot;)
javac -encoding UTF-8 -cp &quot;$CP&quot; -d &quot;$D&quot; &quot;$D/ChangeStreamWiringProbe.java&quot;
java -XX:+EnableDyna</tspan><tspan x="24" dy="22">micAgentLoading -Dlogback.configurationFile=&quot;$D/logback-off.xml&quot; \
-Dstdout.encoding=UTF-8 -cp &quot;$D:$CP&quot; ChangeStreamWi</tspan><tspan x="24" dy="22">ringProbe 2&gt;/dev/null</tspan></text>
<text x="24" y="1344" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">cwd: /shared/codebase/clean-architecture-backend-template</tspan></text>
<text x="24" y="1366" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">time: 2026-09-02T08:19:48+00:00 · exit 0</tspan></text>
<line x1="24" y1="1380" x2="1176" y2="1380" stroke="#30363d"/>
<text x="24" y="1420" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[모듈 opt-in 없이]</tspan></text>
<text x="24" y="1442" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 기본 드라이버쪽=없음 소비자=없음 설정빈=없음</tspan></text>
<text x="24" y="1464" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
<text x="24" y="1486" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[opt-in, 리액티브 템플릿만]</tspan></text>
<text x="24" y="1508" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 기본 드라이버쪽=있음 소비자=없음 설정빈=있음 changeStreams()=false</tspan></text>
<text x="24" y="1530" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> change-streams=true 드라이버쪽=있음 소비자=없음 설정빈=있음 changeStreams()=false</tspan></text>
<text x="24" y="1552" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
<text x="24" y="1574" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[opt-in, 배포가 공급해야 하는 다섯을 함께]</tspan></text>
<text x="24" y="1596" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 기본 드라이버쪽=있음 소비자=있음 설정빈=있음 changeStreams()=false</tspan></text>
<text x="24" y="1618" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> change-streams=true 드라이버쪽=있음 소비자=있음 설정빈=있음 changeStreams()=false</tspan></text>
<text x="24" y="1640" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
<text x="24" y="1662" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[opt-in, 리액티브 템플릿 없이]</tspan></text>
<text x="24" y="1684" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 기본 드라이버쪽=없음 소비자=없음 설정빈=있음 changeStreams()=false</tspan></text>
</svg>