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>
150 lines
12 KiB
Plaintext
150 lines
12 KiB
Plaintext
# 가드가 자기 존재 이유로 적는 것
|
|
IdempotencyCapabilityGuard.java:9 /**
|
|
IdempotencyCapabilityGuard.java:10 * The preconditions a mutation must meet before any SQL runs.
|
|
IdempotencyCapabilityGuard.java:11 *
|
|
IdempotencyCapabilityGuard.java:12 * <p>Its own type because these are three separate questions — is the schema sanctioned, is there a
|
|
IdempotencyCapabilityGuard.java:13 * transaction, is it <em>this</em> store's transaction — and the store answered the third one
|
|
IdempotencyCapabilityGuard.java:14 * wrongly. It checked that the thread had an active read-write transaction, which is true whenever
|
|
IdempotencyCapabilityGuard.java:15 * any transaction is open on any data source; the sibling outbox and inbox adapters check {@code
|
|
IdempotencyCapabilityGuard.java:16 * hasResource(dataSource)}, which is the question that actually matters.
|
|
IdempotencyCapabilityGuard.java:17 *
|
|
IdempotencyCapabilityGuard.java:18 * <p>The difference shows up in an application with two data sources: a mutation issued inside the
|
|
IdempotencyCapabilityGuard.java:19 * <em>other</em> one's transaction passed the old check, ran against this store's connection
|
|
IdempotencyCapabilityGuard.java:20 * outside any transaction, and committed independently of the work it was supposed to be atomic
|
|
IdempotencyCapabilityGuard.java:21 * with.
|
|
IdempotencyCapabilityGuard.java:22 */
|
|
IdempotencyCapabilityGuard.java:23 final class IdempotencyCapabilityGuard {
|
|
|
|
# 그 데이터소스 필드는 어떻게 들어오는가
|
|
IdempotencyCapabilityGuard.java:28 private final JdbcOperations jdbc;
|
|
IdempotencyCapabilityGuard.java:29 private final DataSource dataSource;
|
|
IdempotencyCapabilityGuard.java:30 private final String activeCapabilitySql;
|
|
IdempotencyCapabilityGuard.java:31
|
|
IdempotencyCapabilityGuard.java:32 private volatile boolean postgreSqlConfirmed;
|
|
IdempotencyCapabilityGuard.java:33
|
|
IdempotencyCapabilityGuard.java:34 IdempotencyCapabilityGuard(
|
|
IdempotencyCapabilityGuard.java:35 JdbcOperations jdbc, DataSource dataSource, String activeCapabilitySql) {
|
|
IdempotencyCapabilityGuard.java:36 this.jdbc = Objects.requireNonNull(jdbc, "jdbc");
|
|
IdempotencyCapabilityGuard.java:37 this.dataSource = dataSource;
|
|
IdempotencyCapabilityGuard.java:38 this.activeCapabilitySql = Objects.requireNonNull(activeCapabilitySql, "activeCapabilitySql");
|
|
IdempotencyCapabilityGuard.java:39 }
|
|
|
|
# 세 번째 질문을 지금 어떻게 묻는가
|
|
IdempotencyCapabilityGuard.java:87 /**
|
|
IdempotencyCapabilityGuard.java:88 * Fails closed unless this store's own data source is enlisted in a read-write transaction.
|
|
IdempotencyCapabilityGuard.java:89 *
|
|
IdempotencyCapabilityGuard.java:90 * @throws IllegalStateException naming which of the three conditions failed
|
|
IdempotencyCapabilityGuard.java:91 */
|
|
IdempotencyCapabilityGuard.java:92 void requirePrimaryWriteTransaction() {
|
|
IdempotencyCapabilityGuard.java:93 if (!TransactionSynchronizationManager.isActualTransactionActive()) {
|
|
IdempotencyCapabilityGuard.java:94 throw new IllegalStateException(
|
|
IdempotencyCapabilityGuard.java:95 "owner-safe idempotency mutation requires an active primary transaction");
|
|
IdempotencyCapabilityGuard.java:96 }
|
|
IdempotencyCapabilityGuard.java:97 if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
|
|
IdempotencyCapabilityGuard.java:98 throw new IllegalStateException(
|
|
IdempotencyCapabilityGuard.java:99 "owner-safe idempotency mutation requires a read-write transaction");
|
|
IdempotencyCapabilityGuard.java:100 }
|
|
IdempotencyCapabilityGuard.java:101 if (dataSource != null && !TransactionSynchronizationManager.hasResource(dataSource)) {
|
|
IdempotencyCapabilityGuard.java:102 // The exact check the outbox and inbox adapters already make. Without it, a transaction on
|
|
IdempotencyCapabilityGuard.java:103 // another data source satisfied the guard while this store's work committed on its own.
|
|
IdempotencyCapabilityGuard.java:104 throw new IllegalStateException(
|
|
IdempotencyCapabilityGuard.java:105 "owner-safe idempotency mutation requires a transaction bound to this store's data"
|
|
IdempotencyCapabilityGuard.java:106 + " source; the active transaction belongs to another one");
|
|
IdempotencyCapabilityGuard.java:107 }
|
|
IdempotencyCapabilityGuard.java:108 }
|
|
IdempotencyCapabilityGuard.java:109 }
|
|
|
|
# 그 필드 값을 정하는 곳
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:84 public PostgreSqlOwnerSafeIdempotencyStore(JdbcOperations jdbc) {
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:85 this(jdbc, new SecureRandom());
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:86 }
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:87
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:88 PostgreSqlOwnerSafeIdempotencyStore(JdbcOperations jdbc, SecureRandom secureRandom) {
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:89 Objects.requireNonNull(jdbc, "jdbc");
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:90 this.secureRandom = Objects.requireNonNull(secureRandom, "secureRandom");
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:91 this.guard = new IdempotencyCapabilityGuard(jdbc, dataSourceOf(jdbc), ACTIVE_CAPABILITY_SQL);
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:92 this.rows = new IdempotencyRowMapper(jdbc);
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:93 this.claims = new IdempotencyClaimGateway(jdbc);
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:94 this.transitions = new IdempotencyTransitionGateway(jdbc);
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:95 }
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:96
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:97 /**
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:98 * The data source behind this store's operations, when it can be determined.
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:99 *
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:100 * <p>{@code JdbcTemplate} knows its own; a hand-rolled {@code JdbcOperations} may not. The guard
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:101 * treats an unknown data source as "cannot check", which is weaker than the outbox adapter's
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:102 * exact check and still stronger than what was here before — but it is the honest answer when the
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:103 * collaborator genuinely cannot be identified.
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:104 */
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:105 private static javax.sql.DataSource dataSourceOf(JdbcOperations jdbc) {
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:106 return jdbc instanceof org.springframework.jdbc.core.JdbcTemplate template
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:107 ? template.getDataSource()
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:108 : null;
|
|
PostgreSqlOwnerSafeIdempotencyStore.java:109 }
|
|
|
|
# 이 저장소를 만드는 자리 전부와 각각이 넘기는 것
|
|
postgresqlIntegrationTest · PostgreSqlIdempotencyIntegrationTest.java:61 store = new PostgreSqlOwnerSafeIdempotencyStore(jdbc);
|
|
test · OwnerSafeIdempotencyPreconditionTest.java:47 new PostgreSqlOwnerSafeIdempotencyStore(jdbc);
|
|
main · PostgreSqlIdempotencyProviderConfig.java:60 return new PostgreSqlOwnerSafeIdempotencyStore(jdbc);
|
|
그 자리들이 넘기는 JdbcOperations 의 출처 :
|
|
PostgreSqlIdempotencyProviderConfig.java:55 * @param jdbc the primary {@code JdbcOperations}; the store's transaction guard identifies its
|
|
PostgreSqlIdempotencyProviderConfig.java:56 * data source from it so a transaction opened on a different one cannot pass
|
|
PostgreSqlIdempotencyProviderConfig.java:57 */
|
|
PostgreSqlIdempotencyProviderConfig.java:58 @Bean
|
|
PostgreSqlIdempotencyProviderConfig.java:59 IdempotencyStorePortV2 postgreSqlOwnerSafeIdempotencyStore(JdbcOperations jdbc) {
|
|
PostgreSqlIdempotencyProviderConfig.java:60 return new PostgreSqlOwnerSafeIdempotencyStore(jdbc);
|
|
PostgreSqlIdempotencyProviderConfig.java:61 }
|
|
PostgreSqlIdempotencyProviderConfig.java:62
|
|
OwnerSafeIdempotencyPreconditionTest.java:44
|
|
OwnerSafeIdempotencyPreconditionTest.java:45 private final JdbcOperations jdbc = mock(JdbcOperations.class);
|
|
OwnerSafeIdempotencyPreconditionTest.java:46 private final PostgreSqlOwnerSafeIdempotencyStore store =
|
|
OwnerSafeIdempotencyPreconditionTest.java:47 new PostgreSqlOwnerSafeIdempotencyStore(jdbc);
|
|
OwnerSafeIdempotencyPreconditionTest.java:48
|
|
저장소에 JdbcOperations 구현이나 JdbcTemplate 하위 클래스가 있는가 :
|
|
0 건
|
|
|
|
# 세 번째 검사를 덮는 시험이 있는가
|
|
IdempotencyCapabilityGuard.java:106: + " source; the active transaction belongs to another one");
|
|
전제 시험이 단언하는 것 :
|
|
OwnerSafeIdempotencyPreconditionTest.java:50 void clearTransactionState() {
|
|
OwnerSafeIdempotencyPreconditionTest.java:57 void aClaimWithNoTransactionIsRefused() {
|
|
OwnerSafeIdempotencyPreconditionTest.java:67 void aClaimAgainstAnotherVendorIsRefused() throws Exception {
|
|
OwnerSafeIdempotencyPreconditionTest.java:81 void aClaimAgainstAnUnsanctionedStreamIsRefused() throws Exception {
|
|
|
|
# 가드를 부르는 자리
|
|
main · IdempotencyCapabilityGuard.java:55 void requireActiveCapability() {
|
|
main · IdempotencyCapabilityGuard.java:92 void requirePrimaryWriteTransaction() {
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:122 requirePrimaryWriteTransaction();
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:123 requireActiveCapability();
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:176 requirePrimaryWriteTransaction();
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:211 requirePrimaryWriteTransaction();
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:255 requirePrimaryWriteTransaction();
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:303 requirePrimaryWriteTransaction();
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:360 requirePrimaryWriteTransaction();
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:502 private void requireActiveCapability() {
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:503 guard.requireActiveCapability();
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:506 private void requirePrimaryWriteTransaction() {
|
|
main · PostgreSqlOwnerSafeIdempotencyStore.java:507 guard.requirePrimaryWriteTransaction();
|
|
main · PostgreSqlSameStoreInboxAdapter.java:227 requireActiveCapability();
|
|
main · PostgreSqlSameStoreInboxAdapter.java:494 private void requireActiveCapability() {
|
|
main · PostgreSqlImmutableOutboxAppendAdapter.java:166 requireActiveCapability();
|
|
main · PostgreSqlImmutableOutboxAppendAdapter.java:312 private void requireActiveCapability() {
|
|
|
|
# 형제 어댑터는 같은 질문을 어떻게 묻고 데이터소스를 어떻게 받는가
|
|
PostgreSqlImmutableOutboxAppendAdapter :
|
|
PostgreSqlImmutableOutboxAppendAdapter.java:149 private final DataSource dataSource;
|
|
PostgreSqlImmutableOutboxAppendAdapter.java:153 public PostgreSqlImmutableOutboxAppendAdapter(DataSource dataSource) {
|
|
PostgreSqlImmutableOutboxAppendAdapter.java:158 this.dataSource = Objects.requireNonNull(dataSource, "dataSource");
|
|
PostgreSqlImmutableOutboxAppendAdapter.java:320 private void requireSameResourcePrimaryWriteTransaction() {
|
|
PostgreSqlImmutableOutboxAppendAdapter.java:321 if (!TransactionSynchronizationManager.isActualTransactionActive()) {
|
|
PostgreSqlImmutableOutboxAppendAdapter.java:325 if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
|
|
PostgreSqlImmutableOutboxAppendAdapter.java:328 if (!TransactionSynchronizationManager.hasResource(dataSource)) {
|
|
PostgreSqlSameStoreInboxAdapter :
|
|
PostgreSqlSameStoreInboxAdapter.java:199 private final DataSource dataSource;
|
|
PostgreSqlSameStoreInboxAdapter.java:204 public PostgreSqlSameStoreInboxAdapter(DataSource dataSource) {
|
|
PostgreSqlSameStoreInboxAdapter.java:210 this.dataSource = Objects.requireNonNull(dataSource, "dataSource");
|
|
PostgreSqlSameStoreInboxAdapter.java:502 private void requireSameResourcePrimaryWriteTransaction() {
|
|
PostgreSqlSameStoreInboxAdapter.java:503 if (!TransactionSynchronizationManager.isActualTransactionActive()
|
|
PostgreSqlSameStoreInboxAdapter.java:504 || TransactionSynchronizationManager.isCurrentTransactionReadOnly()
|
|
PostgreSqlSameStoreInboxAdapter.java:505 || !TransactionSynchronizationManager.hasResource(dataSource)) {
|