feat(graphql): GraphQL API 실행 플랫폼 구현 (Stable 48 + Advanced 19 Task)
설계 문서(specs/2026-08-12-graphql-api-execution-platform-design.md)와 두 실행 계획서에 선언된 create path 전량을 adapter-inbound-graphql leaf 안에 구현한다. - 계획서 main 클래스 322개 전량, Task별 테스트 클래스 67개(Stable 48 + Advanced 19) 전량. - 설계서의 Stable 16 + Advanced 12 "Gradle 모듈"은 modules.json 이 19개 leaf 정체성을 소유하므로 bounded sub-package 로 매핑한다(선례: httpclient leaf). 모듈 경계는 문서가 아니라 GraphQlStableModule/GraphQlAdvancedModule 값 선언 + GraphQlModuleBoundaryTest 의 실제 소스 스캔으로 기계 검증한다. - architecture/ 규칙은 리플렉션 + 단순명 매칭으로 구현한다. 인바운드 어댑터가 자신이 금지하는 jakarta.persistence/spring-tx 에 의존해야 검사할 수 있다면 본말전도이기 때문. - 부분 실패는 HTTP 200 + partial data, 요청 실패는 4xx. GraphQL over HTTP 초안 status 294 는 의도적으로 미채택(초안 변경이 클라이언트를 깨뜨리므로). - 요청 단위 DB 트랜잭션을 열지 않는다. 커서는 HMAC 서명된 버전 있는 keyset(상수 시간 비교). - DataLoader 는 요청 스코프, 캐시 키는 actor/tenant sha256 지문으로 격리. - Advanced capability 는 전부 기본 비활성. EXPERIMENTAL 등급은 명시 승인 없이 production 활성화가 거부된다. - spring-webflux 는 compileOnly(runtimeClasspath 제외) — MVC 배치가 WebFlux 런타임을 물려받지 않도록. lockfile 이 스코프 제한을 고정. - graphqlPerformanceTest 는 성능 태그가 0개면 실패한다. failOnNoDiscoveredTests 는 태그 필터로 0건이 된 경우를 잡지 못해(Gradle 9.0.0 실측) 결과 검사를 추가했다. 증거 부재를 통과로 위장하지 않기 위한 fail-closed. 검증: graphqlStableTest 404 / graphqlContractTest 9 / graphqlAdvancedTest 141 tests, :adapter:inbound:graphql:check, verifyCleanArchitectureDependencies, CleanArchitectureTest, verifyConfigurationPropertiesProcessor, verifyEnvKeys, verifyPublicPathSnapshot 전부 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3b5aee50e3
commit
b074c1494e
@@ -0,0 +1,95 @@
|
||||
// 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`.
|
||||
//
|
||||
// Lanes (design §24, Stable plan Task 1 / Task 48):
|
||||
// graphqlStableTest Stable platform unit + boundary tests (default lane)
|
||||
// graphqlContractTest cross-module contract suites (@Tag("graphql-contract"))
|
||||
// graphqlAdvancedTest Advanced/Experimental capability tests (@Tag("graphql-advanced"))
|
||||
// graphqlPerformanceTest load/soak/fault scenarios (@Tag("graphql-performance"))
|
||||
//
|
||||
// `graphql-performance` is excluded from the default `test` task so external load and soak work can
|
||||
// never run inside the unit lane.
|
||||
ext.registerGraphQlPlatformTestLanes = { ->
|
||||
String platformPackage = 'dev.caskeleton.adapter.inbound.graphql'
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform {
|
||||
excludeTags 'quarantine', 'graphql-performance'
|
||||
}
|
||||
}
|
||||
|
||||
Closure<Void> configureLane = { org.gradle.api.tasks.testing.Test lane ->
|
||||
lane.group = 'verification'
|
||||
lane.testClassesDirs = sourceSets.test.output.classesDirs
|
||||
lane.classpath = sourceSets.test.runtimeClasspath
|
||||
lane.jvmArgs '-Duser.timezone=UTC'
|
||||
lane.outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
tasks.register('graphqlStableTest', Test) {
|
||||
description = 'Runs the Stable GraphQL platform test lane (Stable plan Task 1-48).'
|
||||
configureLane(it)
|
||||
useJUnitPlatform {
|
||||
excludeTags 'quarantine', 'graphql-performance', 'graphql-advanced'
|
||||
}
|
||||
filter {
|
||||
includeTestsMatching "${platformPackage}.*"
|
||||
failOnNoMatchingTests = true
|
||||
}
|
||||
failOnNoDiscoveredTests = true
|
||||
}
|
||||
|
||||
tasks.register('graphqlContractTest', Test) {
|
||||
description = 'Runs the GraphQL cross-module contract lane (Stable plan Task 47).'
|
||||
configureLane(it)
|
||||
useJUnitPlatform {
|
||||
includeTags 'graphql-contract'
|
||||
excludeTags 'quarantine'
|
||||
}
|
||||
failOnNoDiscoveredTests = true
|
||||
}
|
||||
|
||||
tasks.register('graphqlAdvancedTest', Test) {
|
||||
description = 'Runs the Advanced/Experimental GraphQL capability lane (Advanced plan Task 1-19).'
|
||||
configureLane(it)
|
||||
useJUnitPlatform {
|
||||
includeTags 'graphql-advanced'
|
||||
excludeTags 'quarantine'
|
||||
}
|
||||
failOnNoDiscoveredTests = true
|
||||
}
|
||||
|
||||
tasks.register('graphqlPerformanceTest', Test) {
|
||||
description = 'Runs the GraphQL load, soak and fault scenario lane (design §24.3, §24.4).'
|
||||
configureLane(it)
|
||||
useJUnitPlatform {
|
||||
includeTags 'graphql-performance'
|
||||
excludeTags 'quarantine'
|
||||
}
|
||||
// The Stable gate requires real load/fault evidence before a Stable release claim, so an
|
||||
// empty run here is a missing-evidence condition rather than a pass.
|
||||
//
|
||||
// `failOnNoDiscoveredTests` alone does NOT cover this: it reacts to an empty candidate class
|
||||
// scan, and this lane always scans a non-empty test tree that JUnit then tag-filters down to
|
||||
// zero. Without the explicit result check below the lane reports BUILD SUCCESSFUL while
|
||||
// proving nothing. Verified empirically on Gradle 9.0.0.
|
||||
failOnNoDiscoveredTests = true
|
||||
doLast {
|
||||
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(
|
||||
'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 ' +
|
||||
'lane against the external load environment that owns them.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user