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>
11 KiB
Project Overview
이 문서는 분석 시작 시점의 sizing 스냅샷이다. 최종 결과는
final/document.md, 교차 스코프 종합은analysis/99-cross-scope.md, 모듈별 확정 수치는 각analysis/NN-*.md의 커버리지 원장이 정본이다. 아래 파일/LOC 표와 bounded scope 목록은 갱신하지 않는다 — 스냅샷으로서의 값이 그 정확성이기 때문이다.
분석 기준 revision
- repository:
/shared/codebase/clean-architecture-backend-template - 최초 기준선:
a24ece9cf797f7ea647e33bf846b115208ed1ba5(모듈 01~19) - 최종 기준:
21234e38cdb9a926cbc92bb97a2aee2e4a7d2916— 분석 도중 커밋21234e38("feat: grpc 기능 deep 구현", 2026-08-31)이 gRPC 가족 18 leaf를 추가했다.git diff a24ece9c..HEAD= 400 files / +40,217 / −4이고 변경 경로가src/grpc*·modules.json·src/build.gradle· docs 15개뿐이어서 모듈 01~19는 영향받지 않는다 (analysis/20-grpc-platform.md§0,analysis/99-cross-scope.md§8). - working tree: clean
최종 커버리지
| 등록 leaf (최종) | 62 (최초 스냅샷 시점 44) |
| COMPLETE | 61 |
| EXCLUDED | 1 — sample-portfolio (사용자 지시 2026-08-30) |
| PENDING | 0 |
| 모듈 문서 | analysis/01–20 (20개). messaging 25 leaf는 19, gRPC 18 leaf는 20으로 통합 |
| 교차 스코프 | analysis/99-cross-scope.md |
| 최종 문서 | final/document.md |
분석 단위와 문서 단위가 1:1이 아니다. 아래 "분석할 bounded scopes" 표는 leaf마다 문서 하나를
계획했으나, messaging:* 25개와 grpc:*·grpc-advanced:* 18개는 leaf 경계를 넘는 계약
(capability 선언 → profile validator → 인증 증거 → 지원 문서)이 실제 설계 단위여서 각각 한 문서로
통합했다. state.json의 scopes가 leaf 단위 정본이고, 각 항목의 analysisFile이 담당 문서를 가리킨다.
Build and module map
- Gradle 9.0.0 wrapper 기반 멀티모듈 build이며
src/settings.gradle이 included buildbuild-logic의ca.architecture-registrysettings plugin을 적용한다. src/config/architecture/modules.json이 leaf 존재/경로/허용 project dependency/runtime membership의 SSOT다.- registry leaf 수: 이 스냅샷 시점 44, 최종 62. 숫자는 측정치이며 문서 규칙의 별도 SSOT로 취급하지 않는다 —
modules.json이 SSOT다.
| module | Gradle path | production files/LOC* | test files/LOC* | runtime membership | status |
|---|---|---|---|---|---|
domain-core |
:domain-core |
8 / 107 | 0 / 0 | app-bootstrap, sample-portfolio | COMPLETE |
shared-contract |
:shared-contract |
56 / 2839 | 23 / 2706 | app-bootstrap, sample-portfolio | PENDING |
application-core |
:application-core |
886 / 35798 | 136 / 19751 | app-bootstrap, sample-portfolio | PENDING |
adapter-outbound-support |
:adapter:outbound:support |
5 / 107 | 1 / 89 | app-bootstrap | PENDING |
adapter-outbound-persistence-jpa |
:adapter:outbound:persistence-jpa |
460 / 44594 | 141 / 12581 | app-bootstrap, sample-portfolio | PENDING |
adapter-outbound-persistence-mongo |
:adapter:outbound:persistence-mongo |
353 / 23401 | 139 / 15416 | app-bootstrap | PENDING |
adapter-outbound-identifier |
:adapter:outbound:identifier |
5 / 134 | 2 / 151 | app-bootstrap, sample-portfolio | PENDING |
adapter-outbound-fileserver |
:adapter:outbound:fileserver |
79 / 12723 | 37 / 12043 | app-bootstrap | PENDING |
adapter-outbound-objectstorage |
:adapter:outbound:objectstorage |
154 / 14966 | 49 / 6778 | sample-portfolio | PENDING |
adapter-outbound-cache-redis |
:adapter:outbound:cache-redis |
316 / 33690 | 70 / 15245 | app-bootstrap | PENDING |
adapter-outbound-httpclient |
:adapter:outbound:httpclient |
270 / 15887 | 97 / 8803 | app-bootstrap | PENDING |
adapter-outbound-messaging |
:adapter:outbound:messaging |
35 / 4350 | 19 / 3670 | app-bootstrap | PENDING |
adapter-outbound-notification |
:adapter:outbound:notification |
172 / 14749 | 61 / 10037 | app-bootstrap | PENDING |
adapter-inbound-web |
:adapter:inbound:web |
428 / 29488 | 202 / 23500 | app-bootstrap, sample-portfolio | PENDING |
adapter-inbound-grpc |
:adapter:inbound:grpc |
9 / 650 | 6 / 782 | none | PENDING |
adapter-inbound-graphql |
:adapter:inbound:graphql |
409 / 26477 | 117 / 14745 | app-bootstrap | PENDING |
adapter-inbound-websocket |
:adapter:inbound:websocket |
174 / 13426 | 74 / 10336 | none | PENDING |
app-bootstrap |
:app-bootstrap |
160 / 15096 | 282 / 32724 | app-bootstrap | PENDING |
sample-portfolio |
:sample-portfolio |
210 / 10429 | 63 / 7020 | sample-portfolio | PENDING |
messaging-core-api |
:messaging:messaging-core-api |
86 / 3952 | 8 / 934 | app-bootstrap | PENDING |
messaging-schema-api |
:messaging:messaging-schema-api |
11 / 635 | 3 / 263 | app-bootstrap | PENDING |
messaging-schema-json |
:messaging:messaging-schema-json |
2 / 234 | 3 / 373 | app-bootstrap | PENDING |
messaging-schema-avro |
:messaging:messaging-schema-avro |
3 / 356 | 4 / 463 | none | PENDING |
messaging-schema-protobuf |
:messaging:messaging-schema-protobuf |
3 / 209 | 2 / 278 | none | PENDING |
messaging-cloudevents |
:messaging:messaging-cloudevents |
4 / 243 | 1 / 162 | app-bootstrap | PENDING |
messaging-policy |
:messaging:messaging-policy |
27 / 1744 | 4 / 1236 | app-bootstrap | PENDING |
messaging-transport-spi |
:messaging:messaging-transport-spi |
14 / 783 | 4 / 586 | app-bootstrap | PENDING |
messaging-runtime-core |
:messaging:messaging-runtime-core |
7 / 804 | 4 / 866 | app-bootstrap | PENDING |
messaging-observability |
:messaging:messaging-observability |
10 / 847 | 6 / 827 | app-bootstrap | PENDING |
messaging-security |
:messaging:messaging-security |
13 / 959 | 3 / 475 | app-bootstrap | PENDING |
messaging-kafka |
:messaging:messaging-kafka |
36 / 3711 | 24 / 4087 | app-bootstrap | PENDING |
messaging-kafka-share-experimental |
:messaging:messaging-kafka-share-experimental |
5 / 200 | 1 / 112 | none | PENDING |
messaging-rabbit |
:messaging:messaging-rabbit |
22 / 2624 | 10 / 1727 | app-bootstrap | PENDING |
messaging-reliability-api |
:messaging:messaging-reliability-api |
14 / 822 | 0 / 0 | app-bootstrap | PENDING |
messaging-outbox-jdbc-postgresql |
:messaging:messaging-outbox-jdbc-postgresql |
19 / 2525 | 8 / 2140 | app-bootstrap | PENDING |
messaging-inbox-jdbc-postgresql |
:messaging:messaging-inbox-jdbc-postgresql |
8 / 576 | 4 / 607 | app-bootstrap | PENDING |
messaging-claim-check |
:messaging:messaging-claim-check |
7 / 424 | 3 / 333 | app-bootstrap | PENDING |
messaging-admin-api |
:messaging:messaging-admin-api |
26 / 1619 | 1 / 147 | app-bootstrap | PENDING |
messaging-admin-runtime |
:messaging:messaging-admin-runtime |
13 / 1263 | 6 / 1051 | app-bootstrap | PENDING |
messaging-pulsar-experimental |
:messaging:messaging-pulsar-experimental |
9 / 676 | 2 / 414 | none | PENDING |
messaging-nats-experimental |
:messaging:messaging-nats-experimental |
8 / 768 | 2 / 460 | none | PENDING |
messaging-spring-cloud-stream-bridge |
:messaging:messaging-spring-cloud-stream-bridge |
7 / 516 | 2 / 295 | none | PENDING |
messaging-spring-boot-starter |
:messaging:messaging-spring-boot-starter |
29 / 3590 | 12 / 2384 | app-bootstrap | PENDING |
messaging-testkit |
:messaging:messaging-testkit |
15 / 1246 | 6 / 828 | none | PENDING |
*파일/LOC 측정은 build output을 제외하고 Java/Kotlin/Groovy/proto/avsc/sql/yaml/properties/json 및 module build.gradle 계열을 대상으로 한 초기 scope sizing 값이다. 이후 각 bounded scope의 coverage denominator는 해당 분석에서 다시 확정한다.
Dependency direction
- registry상
domain-core의 허용 project dependency는 0개다. application-core는domain-core,shared-contract를 허용하며, adapter/runtime leaf들은 registry에 명시된 방향으로만 project edge를 가질 수 있다.- root
verifyCleanArchitectureDependencies는 실제api/implementation/compileOnly/runtimeOnlyProjectDependency 집합과 registry allowlist를 비교한다.
Runtime entry points
- registry가 인식하는 runtime composition은
app-bootstrap,sample-portfolio두 개다. domain-core는 두 composition 모두의 membership에 포함된다. 이는 런타임 closure 포함 계약이며,domain-core자체가 framework entry point나 Spring bean을 가진다는 뜻은 아니다.- 각 composition의 실제 wiring/conditional activation은 해당 bounded scope에서 추가 추적한다.
Persistence / messaging / external systems
- 이 overview에서는 registry와 top-level structure만 확정했다. persistence, messaging, cache, object storage, HTTP client 등은 독립 leaf가 존재하며 세부 동작은 아직 분석하지 않았다.
Test topology
- 각 leaf의 테스트 소스 수를 초기 계수했다.
domain-core자체에는 Java test가 없고.gitkeep만 존재한다. - 도메인 모델링 규칙은
app-bootstrap의CleanArchitectureTest가 production classes 전체를 대상으로 cross-module ArchUnit 검증한다.
Configuration and operational surfaces
- top-level Docker Compose variants,
infra/, runtime configuration이 존재하지만 아직 bounded analysis 전이다.
분석할 bounded scopes (계획 — 실제 문서 배치는 위 "최종 커버리지" 참조)
아래는 분석 시작 시점의 계획이며, 실제 산출물은 다음과 같이 통합됐다:
| 계획 | 실제 |
|---|---|
| leaf 1개 = 문서 1개 (44개 문서) | 20개 문서 |
analysis/19-sample-portfolio.md |
EXCLUDED (사용자 지시) |
analysis/20–44: messaging leaf 25개 각각 |
analysis/19-messaging-platform.md 하나로 통합 |
| (계획에 없음 — 분석 도중 추가된 가족) | analysis/20-grpc-platform.md — grpc 18 leaf |
계획 표 원본은 git 이력에 남아 있다.
아직 단정하지 않는 것 (분석 시작 시점의 목록)
아래는 이 개요를 쓴 시점의 미결 목록이다. 각 항목의 현재 상태를 병기한다.
- 각 adapter의 실제 runtime activation 조건과 external system behavior
→ 해소. 각 모듈 문서 §조립/활성화 절 및
99-cross-scope.md§2. - 각 messaging leaf의 production reachability와 experimental/stable 경계
→ 해소.
19-messaging-platform.md§1.1(출하 18 / build-only 7)·§10.1. - persistence migration ownership과 provider-specific guarantees
→ 부분 해소.
05·06·19§7.2가 소유권과 미적용 스트림을 확정했으나, 컨테이너가 필요한 마이그레이션 IT는 실행하지 않았다(99-cross-scope.md§10.1). - project 전체가 모든 문서상의 architectural claim을 만족한다는 결론
→ 부정. 만족하지 않는 지점이
99-cross-scope.md§2~§5에 형태별로 정리돼 있다.