feat: jpa, messaging, notification, mongo, graphql 어댑터터 구현체 추가
This commit is contained in:
@@ -33,11 +33,21 @@ leaf). 즉 이 레포에는 두 패턴이 공존한다:
|
||||
모듈 레코드가 그대로 leaf 명세로 승격될 수 있게 설계해 두었다. 그때까지 모듈 경계는 문서가
|
||||
아니라 기계가 지킨다:
|
||||
|
||||
- `build/GraphQlStableModule` · `build/GraphQlAdvancedModule` 이 모듈 정체성과 허용 의존 edge 를
|
||||
값으로 선언하고, `build/GraphQlBuildModel` 이 실제 소스 트리를 스캔한다.
|
||||
- `build/GraphQlModuleBoundaryTest` 가 (a) Stable 패키지의 `...graphql.advanced` import 금지,
|
||||
(b) `graphql-core-api` 계열의 Spring/GraphQL Java/Reactor/persistence import 금지,
|
||||
(c) Stable 의존 edge 가 Advanced 모듈을 가리키지 않을 것을 강제한다.
|
||||
- main 의 `moduleboundary/GraphQlStableModule` · `moduleboundary/GraphQlAdvancedModule` 이 모듈
|
||||
정체성·purity 등급·허용 의존 edge 를 값으로 선언하고, `moduleboundary/GraphQlModuleBoundary` 가
|
||||
"이 패키지의 주인은 누구인가 / 이 edge 는 선언됐는가"를 답한다.
|
||||
- test 의 `moduleboundary/GraphQlBuildModel` 이 실제 소스 트리를 스캔하고,
|
||||
`moduleboundary/GraphQlModuleBoundaryTest` 가 (a) Stable 패키지의 `...graphql.advanced` import
|
||||
금지, (b) `CORE` 등급 모듈의 Spring/GraphQL Java/Reactor/Micrometer/Jakarta import 금지,
|
||||
(c) 선언되지 않은 cross-module edge 금지, (d) 미등록 패키지 금지, (e) 선언만 있고 소스가 없는
|
||||
모듈 금지를 강제한다. 각 규칙은 **거부되는 합성 트리(negative fixture)** 를 함께 가진다.
|
||||
|
||||
**패키지 이름은 `build` 가 아니라 `moduleboundary` 다.** `src/.gitignore:2` 의 anchor 없는
|
||||
`build/` 규칙은 Gradle 산출물과 Java 패키지를 구분하지 못해서, 예전에 이 경계 모델 전체를
|
||||
커밋에서 삼켰다(프로덕션 코드는 계속 import 하고, 작성자 작업본만 컴파일되고, fresh checkout
|
||||
은 7개 오류로 깨졌다). 레포 전역 `verifyNoIgnoredSourcePackages` 가 이 부류를 막고,
|
||||
`graphqlStableTest` 의 required-class 검사가 "경계 테스트만 조용히 사라지고 레인은 green" 인
|
||||
나머지 절반을 막는다.
|
||||
|
||||
**새 플랫폼 sub-package 를 추가할 때는 반드시 해당 모듈 레코드에 정체성과 허용 edge 를 먼저
|
||||
등록한다.** 등록 없이 추가된 패키지는 경계 테스트가 실패시킨다.
|
||||
@@ -59,13 +69,25 @@ leaf). 즉 이 레포에는 두 패턴이 공존한다:
|
||||
## Allowed
|
||||
|
||||
- `:application-core`, `:domain-core`, `:shared-contract`.
|
||||
- `spring-boot-starter-graphql`, `spring-boot-starter-web`, `jackson-datatype-jsr310`
|
||||
(전부 Spring Boot BOM 관리 — 버전 명시 없음).
|
||||
- test scope 에 한해 실제 HTTP 인증/CORS qualification 용 `spring-boot-starter-security`.
|
||||
- `compileOnly` 로만 `spring-webflux` — REACTIVE_WEBFLUX 전송 프로파일(`http/webflux/`)을
|
||||
컴파일하기 위한 것이고, 의도적으로 `runtimeClasspath` 에서 제외한다. MVC 배치에 WebFlux 를
|
||||
끌어들이지 않기 위함이며 `gradle.lockfile` 이 이 스코프 제한을 고정한다
|
||||
(`spring-webflux:...=compileClasspath,testCompileClasspath,testRuntimeClasspath`).
|
||||
- `spring-boot-starter-graphql` (Spring Boot BOM 관리 — 버전 명시 없음).
|
||||
- test scope 에 한해 `spring-boot-starter-web`(random-port 전송 테스트용),
|
||||
`spring-boot-starter-security`(HTTP 인증/CORS qualification 용),
|
||||
`io.micrometer:micrometer-core`(실제 `MeterRegistry` 로 metric label cardinality 를 **측정**).
|
||||
- `java-test-fixtures` — 계약 스위트·통합 fixture·in-memory 스텁은 `src/testFixtures/java` 가
|
||||
소유하고 production jar 에 들어가지 않는다. 모듈 경계 스캐너
|
||||
(`moduleboundary/GraphQlBuildModel`)는 main 과 testFixtures 를 **함께** 스캔한다: 아티팩트가
|
||||
갈렸다고 패키지 경계 규칙까지 갈리면, 규칙이 조용히 절반만 남는다.
|
||||
|
||||
**서버는 이 leaf 가 고르지 않는다.** production 파일 중 `org.springframework.web`·
|
||||
`jakarta.servlet`·`org.springframework.http` 을 import 하는 것은 **하나도 없다**. 예전에는
|
||||
`spring-boot-starter-web` 을 production `implementation` 으로 두어 모든 adopter 의
|
||||
runtimeClasspath 에 Tomcat 을 올리면서, 동시에 같은 artifact 가 `REACTIVE_WEBFLUX` 실행
|
||||
프로파일을 표방했다 — leaf 와 함께 servlet 컨테이너가 따라오므로 결코 성립할 수 없는 조합이었다.
|
||||
|
||||
이제 서버 선택은 composition root 의 결정이고, `gradle.lockfile` 이 이를 고정한다
|
||||
(`spring-boot-starter-web`·`spring-webmvc`·`spring-webflux`·`tomcat-embed-*` 전부
|
||||
`testCompileClasspath,testRuntimeClasspath` 만). `GraphQlRuntimeTransport` 가 실제 실행 중인
|
||||
서버를 감지해 `backend.graphql.execution-profile` 과 어긋나면 **부팅을 거부**한다.
|
||||
- `annotationProcessor` 로 `spring-boot-configuration-processor` — `GraphQlPlatformProperties` 가
|
||||
`@ConfigurationProperties` 이므로 레포 전역 `verifyConfigurationPropertiesProcessor` 패리티
|
||||
게이트가 이 선언을 요구한다.
|
||||
@@ -98,12 +120,30 @@ feature 는 `ApiErrorCarrier` 를 구현한 예외(자신의 `ApiErrorCode` 를
|
||||
현재 sample 에 feature GraphQL schema/controller/resolver 가 있다고 가정하지 않는다. 이 leaf 는
|
||||
health 스키마만 소유한다.
|
||||
|
||||
## 구현된 플랫폼 범위
|
||||
## 구현된 플랫폼 범위 — 등급으로 말한다
|
||||
|
||||
query depth/cost 제한(`cost/`), persisted operation(`advanced/persisted/`),
|
||||
DataLoader/batching(`dataloader/`), subscription(`advanced/subscription/`, `advanced/websocket/`,
|
||||
`advanced/sse/`)은 **더 이상 미구현이 아니다.** 다만 이들은 정책·계약·검증 기계이며, 실제
|
||||
composition root 가 채택할 때 정책 값과 인증/인가 빈을 함께 제공해야 한다.
|
||||
"구현됐다" 는 네 가지 서로 다른 사실을 한 단어로 덮는다. 그래서 capability 마다 아래 등급을
|
||||
쓰고, **현재 등급보다 높게 표현하지 않는다.**
|
||||
|
||||
| 등급 | 의미 |
|
||||
| --- | --- |
|
||||
| `modelled` | 정책·계약 객체가 있고 단위 테스트가 있다. 요청 경로에는 없다. |
|
||||
| `wired` | Spring 실행 경로에 연결돼 있고, 실제 endpoint 테스트가 그 사실을 증명한다. |
|
||||
| `integration-verified` | 실제 외부 시스템(datastore/broker) 과의 통합 증거가 있다. |
|
||||
| `production-verified` | 실부하·장애 시나리오 증거가 있다. |
|
||||
|
||||
| Capability | 등급 | 증거 |
|
||||
| --- | --- | --- |
|
||||
| 실행 파이프라인 / 인가 / cost 예산 | `wired` | `runtime/GraphQlPlatformExecutionPathTest` (random-port, 거부 시 resolver 호출 0회) |
|
||||
| depth/complexity 제한 (`cost/`) | `wired` | 같은 테스트의 depth/alias/complexity 케이스 |
|
||||
| preparsed document cache (`execution/`) | `wired` | `GraphQlPreparsedDocumentAdapter` + 같은 테스트의 캐시 hit 케이스 |
|
||||
| 커스텀 scalar (`scalar/`) | `wired` | 같은 테스트의 scalar coercion 케이스 |
|
||||
| 요청 크기/Accept 협상 (`http/`) | `wired` | `GraphQlRequestBoundsTest`, `GraphQlAcceptNegotiationTest` |
|
||||
| DataLoader/batching (`dataloader/`) | `wired` | `runtime/GraphQlBatchLoaderRegistrar` + `dataloader/GraphQlBatchContractTest` |
|
||||
| persisted operation (`advanced/persisted/`) | `modelled` | 중립 `OperationalRecordStorePort` 기반 레지스트리 + 방향성 테스트. durable 구현체는 미제공 |
|
||||
| subscription / WebSocket / SSE / RSocket | `modelled` | 정책·상태기계 단위 테스트만. Spring transport handler 는 없다(그래서 타입 이름도 `*Admission` 이다) |
|
||||
| federation / incremental / codegen / compat | `modelled` | 단위 테스트만 |
|
||||
| 실부하·장애 | 미달성 | `graphqlPerformanceTest` 레인이 자리를 예약, 증거 없으면 릴리스 게이트가 거부 |
|
||||
|
||||
여전히 미구현인 것:
|
||||
|
||||
@@ -114,7 +154,13 @@ composition root 가 채택할 때 정책 값과 인증/인가 빈을 함께 제
|
||||
생성한다(`graphqlPerformanceTest` 레인이 그 자리를 예약해 둔다).
|
||||
- 실제 datastore 통합 증거 — `testkit/GraphQlJpaIntegrationFixture` /
|
||||
`GraphQlMongoIntegrationFixture` 가 계약을 정의하고 `GraphQlStorageIntegrationEvidence` 가
|
||||
증거를 요구한다. 실 datastore 기동은 persistence leaf 의 책임 범위다.
|
||||
증거를 요구한다. 실 datastore 기동은 persistence leaf 의 책임 범위다. 이 testkit 은
|
||||
**production jar 에 없다** — `src/testFixtures/java` 에 살고 `verifyGraphQlProductionJar` 가
|
||||
그 사실을 jar 내용으로 확인한다.
|
||||
- persisted operation 의 durable 저장 구현체 — 이 leaf 는 중립 계약
|
||||
`dev.caskeleton.shared.opstore.OperationalRecordStorePort` 에만 의존하고 key/value 매핑만
|
||||
소유한다. Postgres/Redis 구현체는 **그 중립 계약을** 구현하며, 이 leaf 의 타입을 구현하지
|
||||
않는다(그랬다면 인프라 → 인바운드 전송으로 의존이 뒤집힌다).
|
||||
- Advanced capability 는 전부 **기본 비활성**이다(`advanced/bootstrap/GraphQlAdvancedFeatureFlags`).
|
||||
EXPERIMENTAL 등급(RSocket, incremental delivery, HTTP GET draft)은 명시적 승인 없이는
|
||||
`GraphQlAdvancedModuleGuard` 가 production 활성화를 거부한다.
|
||||
@@ -133,11 +179,30 @@ cd src
|
||||
`quarantine`·`graphql-performance` 태그를 제외한다:
|
||||
|
||||
```bash
|
||||
./gradlew :adapter:inbound:graphql:graphqlStableTest --console=plain # 404 tests
|
||||
./gradlew :adapter:inbound:graphql:graphqlStableTest --console=plain # 551 tests
|
||||
./gradlew :adapter:inbound:graphql:graphqlContractTest --console=plain # 9 tests
|
||||
./gradlew :adapter:inbound:graphql:graphqlAdvancedTest --console=plain # 141 tests
|
||||
./gradlew :adapter:inbound:graphql:graphqlAdvancedTest --console=plain # 152 tests
|
||||
./gradlew :adapter:inbound:graphql:graphqlPerformanceTest --console=plain # 실부하 인프라 필요
|
||||
```
|
||||
|
||||
`graphqlPerformanceTest` 는 `@Tag("graphql-performance")` 가 하나도 없으면 **실패한다** — 이는
|
||||
버그가 아니라 "성능 증거 없음"을 통과로 위장하지 않기 위한 fail-closed 설계다.
|
||||
|
||||
위 숫자는 `build/test-results/<lane>/*.xml` 의 실제 실행 결과다(기본 `test` 703, transport
|
||||
qualification 8). 문서에 옮겨 적은 숫자는 반드시 마지막 green 실행에서 다시 읽어 갱신한다 —
|
||||
컴파일이 깨진 채로 남은 과거 숫자는 통과 증거가 아니라 통과했다는 인상일 뿐이다.
|
||||
|
||||
## 아티팩트 게이트
|
||||
|
||||
```bash
|
||||
./gradlew :adapter:inbound:graphql:verifyGraphQlProductionJar --console=plain
|
||||
./gradlew :adapter:inbound:graphql:verifyGraphQlApiSurface --console=plain
|
||||
```
|
||||
|
||||
- `verifyGraphQlProductionJar` — production jar 에 `testkit`/`InMemory`/`Fixture`/`TestContext`
|
||||
클래스가 하나라도 있으면 실패한다. 계약 스위트와 in-memory 스텁은 `src/testFixtures/java` 가
|
||||
소유한다.
|
||||
- `verifyGraphQlApiSurface` — `docs/architecture/graphql-api-surface.txt` 스냅샷과 실제 public
|
||||
타입 목록이 다르면 실패한다. 단일 jar 안에서 `public` 은 모든 adopter 에게 public 이므로,
|
||||
표면 증가는 리뷰 결정이지 빌드 부산물이 아니다. 승인 후:
|
||||
`./gradlew :adapter:inbound:graphql:updateGraphQlApiSurface -PapproveGraphQlApiSurfaceChange`.
|
||||
|
||||
Reference in New Issue
Block a user