From c3043e530a604315c4df341b87b5470c7617ea03 Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Fri, 14 Aug 2026 16:19:48 +0900 Subject: [PATCH] =?UTF-8?q?docs(graphql):=2043-leaf=20=EB=A0=88=EC=A7=80?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A6=AC=20=EC=82=AC=EC=8B=A4=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20sub-package=20=EA=B7=BC=EA=B1=B0=EB=A5=BC?= =?UTF-8?q?=20=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main 통합으로 messaging 플랫폼이 24개 leaf 를 modules.json 에 등록한 것이 드러났다 (현재 총 43개 leaf). 따라서 "레지스트리는 정확히 19개로 고정되어 있고 확장하면 게이트가 깨진다"는 기존 서술은 사실과 다르다. - 레지스트리는 확장 가능하며, 자매 플랫폼 messaging 은 정반대 패턴(leaf 등록)을 택했다. - graphql 의 sub-package 매핑은 "레지스트리가 닫혀서"가 아니라 "GraphQL 표면은 하나의 인바운드 전송 경계이고 그 내부 분할을 레포 전역 SSOT 까지 올리지 않는다"는 선택으로 다시 서술한다. - 두 패턴이 공존하므로 통일 여부는 미결 아키텍처 결정으로 명시한다. 모듈 레코드는 그대로 leaf 명세로 승격 가능한 형태라 분해 비용은 낮게 유지된다. 루트 CLAUDE.md 도 여전히 "exactly 19 leaf identities" 라고 적혀 있으나 이는 messaging 머지에서 비롯된 선행 불일치이므로 이 커밋 범위 밖으로 두고 보고한다. Co-Authored-By: Claude Opus 5 (1M context) --- src/adapter/inbound/graphql/CLAUDE.md | 21 +++++++++++++++---- src/adapter/inbound/graphql/README.md | 20 ++++++++++++------ .../graphql-platform-conventions.gradle | 17 +++++++++------ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/adapter/inbound/graphql/CLAUDE.md b/src/adapter/inbound/graphql/CLAUDE.md index 4f42e93f..c49e1a47 100644 --- a/src/adapter/inbound/graphql/CLAUDE.md +++ b/src/adapter/inbound/graphql/CLAUDE.md @@ -15,10 +15,23 @@ Package root: `dev.caskeleton.adapter.inbound.graphql`. ## 플랫폼 모듈 = sub-package (Gradle 모듈 아님) -GraphQL 플랫폼 설계서는 Stable 16개 + Advanced 12개 "모듈"을 말하지만, 이 레포의 leaf 정체성 -SSOT 는 `src/config/architecture/modules.json` 이고 거기에는 **정확히 19개 leaf** 만 존재한다. -따라서 설계서의 28개 모듈은 이 leaf 안의 **bounded sub-package** 로 매핑한다(선례: httpclient -leaf). 대신 모듈 경계는 문서가 아니라 기계가 지킨다: +GraphQL 플랫폼 설계서는 Stable 16개 + Advanced 12개 "모듈"을 말하지만, 이 구현은 그 28개를 이 +leaf 안의 **bounded sub-package** 로 매핑한다(선례: httpclient leaf 가 366개 java 파일을 같은 +방식으로 담는다). GraphQL 표면은 하나의 인바운드 전송 경계이고, 그 내부 분할을 leaf 정체성 +SSOT(`src/config/architecture/modules.json`)까지 밀어올리지 않는다는 선택이다. + +**이는 레지스트리가 닫혀 있어서가 아니다.** `modules.json` 은 확장 가능하며, 실제로 자매 +플랫폼인 messaging 은 **정반대 선택**을 해서 24개 leaf 를 레지스트리에 등록했다(현재 총 43개 +leaf). 즉 이 레포에는 두 패턴이 공존한다: + +| | 방식 | 경계 강제 | +| --- | --- | --- | +| messaging | 레지스트리에 leaf 등록 | Gradle 의존 게이트 | +| graphql | 단일 leaf 내 sub-package | 아래 경계 테스트 | + +어느 쪽으로 통일할지는 **미결 아키텍처 결정**이다. graphql 을 leaf 로 분해하려면 이 문서의 +모듈 레코드가 그대로 leaf 명세로 승격될 수 있게 설계해 두었다. 그때까지 모듈 경계는 문서가 +아니라 기계가 지킨다: - `build/GraphQlStableModule` · `build/GraphQlAdvancedModule` 이 모듈 정체성과 허용 의존 edge 를 값으로 선언하고, `build/GraphQlBuildModel` 이 실제 소스 트리를 스캔한다. diff --git a/src/adapter/inbound/graphql/README.md b/src/adapter/inbound/graphql/README.md index 1337763f..61d942ec 100644 --- a/src/adapter/inbound/graphql/README.md +++ b/src/adapter/inbound/graphql/README.md @@ -94,13 +94,21 @@ composition root 는 이 leaf 를 채택할 때 인증/인가 및 CORS 정책을 ## 설계서의 "모듈"을 Gradle 모듈로 만들지 않은 이유 -설계서는 Stable 16 + Advanced 12 = 28개 Gradle 모듈을 전제한다. 그러나 이 레포의 leaf 정체성 -SSOT 는 `src/config/architecture/modules.json` 이고 **정확히 19개** 로 고정되어 있다. 28개를 -추가하면 레지스트리·`settings.gradle` fail-closed 검증·의존 게이트가 전부 깨지고, 이는 Prime -Directive 5번(HARD-STOP)에 정면으로 저촉된다. +설계서는 Stable 16 + Advanced 12 = 28개 Gradle 모듈을 전제한다. 구현 시점의 레지스트리 +(`src/config/architecture/modules.json`)는 19개 leaf 였고, 28개를 추가하는 것은 레지스트리· +`settings.gradle` fail-closed 검증·의존 게이트를 동시에 건드리는 변경이다. GraphQL 표면은 하나의 +인바운드 전송 경계이므로 그 **내부** 분할을 레포 전역 SSOT 까지 밀어올리지 않기로 하고, +**모듈 = bounded sub-package** 로 매핑했다(선례: httpclient leaf 가 366개 java 파일을 같은 방식으로 +담는다). -그래서 **모듈 = bounded sub-package** 로 매핑했다(선례: httpclient leaf 가 366개 java 파일을 같은 -방식으로 담는다). 대신 "패키지는 경계가 아니다"라는 통상의 약점을 기계 검증으로 메웠다 — +**정직하게 기록해 둘 반례:** 이후 main 에 통합된 자매 플랫폼 messaging 은 정반대로 +**24개 leaf 를 레지스트리에 등록**했다(현재 총 43개 leaf). 즉 레지스트리는 닫혀 있지 않았고, +"확장하면 게이트가 깨진다"는 전제는 과했다. 두 플랫폼이 서로 다른 패턴을 쓰고 있으므로 +**어느 쪽으로 통일할지는 미결 아키텍처 결정**이다. graphql 쪽은 모듈 레코드 +(`GraphQlStableModule`/`GraphQlAdvancedModule`)가 그대로 leaf 명세로 승격될 수 있는 형태라 +분해 비용은 낮게 유지했다. + +어느 패턴이든 "패키지는 경계가 아니다"라는 약점은 기계 검증으로 메웠다 — `build/GraphQlStableModule`·`GraphQlAdvancedModule` 이 모듈 정체성과 허용 edge 를 값으로 선언하고, `GraphQlModuleBoundaryTest` 가 **실제 소스 트리를 스캔**해 Stable→Advanced import, core-api 의 프레임워크 import, Stable edge 의 Advanced 참조를 실패시킨다. Gradle 이 해주던 일을 테스트가 diff --git a/src/gradle/graphql-platform-conventions.gradle b/src/gradle/graphql-platform-conventions.gradle index 02003c86..ed95ed0c 100644 --- a/src/gradle/graphql-platform-conventions.gradle +++ b/src/gradle/graphql-platform-conventions.gradle @@ -1,11 +1,16 @@ // GraphQL API 실행 플랫폼 verification lanes. // // The GraphQL platform design package ships a 16-module Stable map and a 12-module Advanced map. -// This repository's registry (`src/config/architecture/modules.json`) owns exactly 19 leaf -// identities, so those maps are realised as bounded PACKAGES inside the single registered -// `adapter-inbound-graphql` leaf — the same mapping the httpclient capability already uses. The -// module identities, their allowed internal dependency edges and the Stable→Advanced isolation rule -// stay machine-checked through `GraphQlBuildModel` and `GraphQlModuleBoundaryTest`. +// Those maps are realised as bounded PACKAGES inside the single registered `adapter-inbound-graphql` +// leaf — the same mapping the httpclient capability already uses — because the GraphQL surface is one +// inbound transport boundary whose internal split does not have to reach the repository-wide leaf +// registry (`src/config/architecture/modules.json`). +// +// Note the counter-example: the sibling messaging platform made the opposite call and registered 24 +// leaves of its own. The registry is extensible, so this is a deliberate trade-off, not a constraint. +// Whichever pattern the repository standardises on, the module identities, their allowed internal +// dependency edges and the Stable→Advanced isolation rule stay machine-checked through +// `GraphQlBuildModel` and `GraphQlModuleBoundaryTest`. // // Lanes (design §24, Stable plan Task 1 / Task 48): // graphqlStableTest Stable platform unit + boundary tests (default lane) @@ -84,7 +89,7 @@ ext.registerGraphQlPlatformTestLanes = { -> File resultsDir = reports.junitXml.outputLocation.get().asFile File[] executed = resultsDir.listFiles({ File file -> file.name.endsWith('.xml') } as FileFilter) if (executed == null || executed.length == 0) { - throw new GradleException( + throw new org.gradle.api.GradleException( 'graphqlPerformanceTest ran no scenario: the Stable release gate requires real ' + 'load, soak and fault evidence, so an empty performance lane is a missing-evidence ' + 'failure, not a pass. Register @Tag("graphql-performance") scenarios or run the ' +