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>
142 lines
11 KiB
Markdown
142 lines
11 KiB
Markdown
# 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 build `build-logic`의 `ca.architecture-registry` settings 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/runtimeOnly` ProjectDependency 집합과 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에 형태별로 정리돼 있다.
|