654 lines
22 KiB
Markdown
654 lines
22 KiB
Markdown
# VD-26: Persisted GraphQL operation
|
|
|
|
- 상태: Accepted design — reference runtime implementation pending
|
|
- 결정일: 2026-07-28
|
|
- provider-neutral GraphQL reference adapter:
|
|
`DESIGNED_NOT_IMPLEMENTED`
|
|
- product GraphQL composition: `NOT_SELECTED`
|
|
- batching/subscription/`@defer`/`@stream`: `NOT_SELECTED`
|
|
- normalized GraphQL cache: `NOT_SELECTED`
|
|
- 관련 결정: VD-13, VD-23, VD-24, VD-25, VD-28
|
|
- 상세 설계:
|
|
[API contract, Schema, Mapper와 Server State](../api-contract-schema-mapper-and-server-state.md)
|
|
|
|
## 배경
|
|
|
|
현재 source, package direct dependency, config와 test에는 GraphQL runtime,
|
|
operation document/codegen, persisted manifest나 endpoint provider가 없다.
|
|
lockfile의 transitive `graphql` package는 MSW 개발 의존성일 뿐 capability
|
|
구현 증거가 아니다.
|
|
|
|
GraphQL은 임의 query string을 보내는 범용 API escape hatch로 도입하지 않는다.
|
|
제품이 여러 backend aggregate를 화면별 shape로 조회해야 하고 schema/router,
|
|
field authorization, persisted allowlist와 cost budget을 운영할 수 있을 때만
|
|
bounded-context operation family로 선택한다.
|
|
|
|
## 결정
|
|
|
|
### 1. Production GraphQL은 persisted operation only다
|
|
|
|
```text
|
|
semantic application query/command
|
|
-> registered GraphqlOperationDefinition
|
|
-> fixed endpoint
|
|
-> persisted operation ID/hash
|
|
-> validated variables
|
|
-> bounded GraphQL response decoder
|
|
-> operation data schema
|
|
-> boundary mapper
|
|
-> application projection
|
|
-> TanStack Query or command result
|
|
```
|
|
|
|
production runtime은 다음을 받지 않는다.
|
|
|
|
- arbitrary GraphQL document
|
|
- caller-provided operation name/hash
|
|
- arbitrary endpoint/header
|
|
- generated SDK selection set builder
|
|
- field/fragment string
|
|
|
|
### 2. Operation artifact
|
|
|
|
```text
|
|
PersistedGraphqlOperationV1
|
|
protocol = PERSISTED_GRAPHQL_V1
|
|
semanticOperationId
|
|
operationName
|
|
operationKind = QUERY | MUTATION
|
|
canonicalDocumentSha256
|
|
persistedOperationId
|
|
schemaArtifactId
|
|
schemaDigest
|
|
variablesSchemaId
|
|
dataSchemaId
|
|
mapperId
|
|
errorProfileId
|
|
partialDataPolicy
|
|
endpointId
|
|
graphqlHttpProfileRevision
|
|
persistedEnvelopeProfileId
|
|
responseStatusMediaProfileId
|
|
authProfileId
|
|
csrfProfileId
|
|
replayPolicy
|
|
deadlineProfileId
|
|
retryProfileId
|
|
serverStateProfileId | null
|
|
maxVariablesBytes
|
|
maxResponseBytes
|
|
maxErrorCount
|
|
maxCost
|
|
maxDepth
|
|
maxAliases
|
|
owner
|
|
```
|
|
|
|
canonical document는 build artifact이고 runtime string이 아니다. stable operation
|
|
ID와 hash는 schema/operation manifest에 binding한다.
|
|
|
|
manifest 생성:
|
|
|
|
```text
|
|
authenticated immutable schema
|
|
-> named operation sources
|
|
-> parse/validate against schema
|
|
-> canonical document
|
|
-> operation hash/ID
|
|
-> variables/result type generation
|
|
-> runtime codec manifest
|
|
-> mapper/query profile binding
|
|
-> persisted operation manifest
|
|
```
|
|
|
|
### 3. Schema와 codegen
|
|
|
|
- schema source URL에서 normal build마다 latest를 받지 않는다.
|
|
- authenticated explicit update workflow가 immutable artifact와 provenance를
|
|
저장한다.
|
|
- schema/source/operation manifest digest를 release contract set에 binding한다.
|
|
- anonymous operation, duplicate operation name와 invalid fragment를 거절한다.
|
|
- generator, plugins, Node와 runtime version을 pin한다.
|
|
- clean checkout regenerate diff가 0이어야 한다.
|
|
- schema breaking diff, operation validation, deprecated field budget와 generated
|
|
output digest를 CI gate로 둔다.
|
|
- generated type은 adapter-private DTO다.
|
|
- generated TypeScript type만 믿지 않고 variables/data runtime codec과 mapper를
|
|
유지한다.
|
|
- schema introspection을 production에서 끄는 결정은 server 보안 옵션일 뿐
|
|
authorization/cost control을 대체하지 않는다.
|
|
|
|
### 4. Endpoint와 HTTP profile
|
|
|
|
```text
|
|
GraphqlProviderProfile
|
|
endpointId
|
|
fixedHttpsUrl
|
|
graphqlHttpProfileRevision
|
|
persistedEnvelopeProfileId
|
|
methodPolicy
|
|
credentialsMode
|
|
corsProfile
|
|
referrerPolicy
|
|
redirect = ERROR
|
|
mediaProfile
|
|
```
|
|
|
|
GraphQL-over-HTTP draft를 움직이는 implicit `latest`로 구현하지 않는다. selected
|
|
revision의 request/response/status 규칙과 provider의 persisted-operation
|
|
extension을 immutable profile/fixture에 고정한다. persisted ID/hash-only envelope는
|
|
표준 request의 required `query` field를 생략하는 provider extension일 수 있으므로
|
|
generic GraphQL-over-HTTP compliance로 가장하지 않는다.
|
|
|
|
private query와 mutation은 POST가 기본이다.
|
|
|
|
GET은 다음을 모두 만족하는 public read profile에서만 선택한다.
|
|
|
|
- persisted ID/hash와 non-sensitive bounded variables
|
|
- URL byte ceiling
|
|
- no credential/private representation 또는 명시된 safe cache contract
|
|
- exact cache key/Vary/CDN policy
|
|
- mutation 아님
|
|
|
|
raw document와 sensitive variables를 URL에 넣지 않는다.
|
|
|
|
request `Content-Type: application/json`과
|
|
`Accept: application/graphql-response+json`을 기본 exact profile로 둔다.
|
|
`application/json` response 지원은 legacy provider profile로 분리한다. caller가
|
|
`fetch` option, headers와 credentials를 override하지 않는다.
|
|
|
|
status/media matrix:
|
|
|
|
- final URL/origin과 media/body ceiling을 먼저 확인한다.
|
|
- `application/graphql-response+json`은 profile이 허용한 HTTP status 전체에서
|
|
bounded GraphQL envelope를 먼저 decode하고 selected revision의 status/body
|
|
불변조건을 교차 검증한다.
|
|
- non-null `data`가 있는 response는 selected revision이 요구하는 2xx여야 한다.
|
|
no-data/error와 partial response의 status는 pinned revision/provider fixture와
|
|
exact match해야 한다.
|
|
- legacy `application/json`은 허용된 2xx body만 GraphQL envelope로 신뢰한다.
|
|
non-2xx body는 intermediary일 수 있으므로 GraphQL error/extensions로
|
|
해석하지 않고 bounded generic HTTP failure로 닫는다.
|
|
|
|
### 5. Request envelope
|
|
|
|
wire shape는 provider의 persisted-envelope extension이 versioned codec으로
|
|
고정한다. 최소 의미:
|
|
|
|
```text
|
|
protocol
|
|
persisted operation ID
|
|
canonical document hash
|
|
operation name
|
|
validated variables
|
|
client contract manifest version
|
|
```
|
|
|
|
full document는 포함하지 않는다.
|
|
|
|
provider가 ID/hash-only envelope를 지원하지 않으면 이 capability를 그 endpoint에
|
|
compose하지 않는다. production에서 표준 `query` field를 채우기 위해 full
|
|
document fallback을 보내는 것으로 우회하지 않는다.
|
|
|
|
variables:
|
|
|
|
- request runtime schema의 parsed output만 사용
|
|
- unknown field 거절
|
|
- depth/node/string/list/encoded byte ceiling
|
|
- File/Blob/stream/native/generated class 금지
|
|
- ID/decimal/int64/time semantics는 VD-24
|
|
- secret/credential를 variable로 전달하는 operation 금지
|
|
|
|
### 6. APQ와 manifest miss
|
|
|
|
runtime Automatic Persisted Query negotiation을 production default로 사용하지
|
|
않는다.
|
|
|
|
```text
|
|
persisted miss/hash mismatch
|
|
-> body/reader cancel
|
|
-> PERSISTED_OPERATION_MISMATCH
|
|
-> operation traffic disable or coherent manifest recovery
|
|
```
|
|
|
|
hash miss 뒤 full document를 자동 전송하면 server allowlist와 cost governance를
|
|
우회할 수 있다. trusted development profile에서만 explicit opt-in 가능하며
|
|
production promotion 증거로 사용하지 않는다.
|
|
|
|
frontend manifest와 router manifest의 N/N-1 rollout을 먼저 증명한다.
|
|
|
|
### 7. Total deadline, cancellation과 retry
|
|
|
|
VD-23 common logical deadline을 사용한다.
|
|
|
|
- credential/CSRF attach
|
|
- network attempts/backoff
|
|
- response read/JSON parse
|
|
- GraphQL envelope/data/error validation
|
|
- mapper
|
|
|
|
Query retry와 GraphQL transport retry를 중복하지 않는다.
|
|
|
|
retry:
|
|
|
|
- idempotent query의 selected network/408/429/502/503/504
|
|
- keyed mutation은 backend idempotency evidence가 있을 때만
|
|
- GraphQL validation, persisted miss, cost/depth, schema/data/error mismatch는
|
|
retry하지 않음
|
|
- HTTP 200 GraphQL business error를 transient network failure로 자동 retry하지 않음
|
|
- UNAUTHENTICATED recovery는 safe query/keyed mutation만 same logical binding으로
|
|
한 번
|
|
|
|
AbortSignal은 fetch와 body/incremental reader를 cancel한다. local cancel이 mutation
|
|
미적용을 의미하지 않으며 ambiguous effect는 status/reconcile contract로 닫는다.
|
|
|
|
### 8. Response decoder
|
|
|
|
```text
|
|
HTTP response
|
|
-> final URL/origin/media/header
|
|
-> present/valid Content-Length advisory preflight
|
|
-> bounded stream reader
|
|
-> decoded byte/depth/node/string/list cap
|
|
-> GraphQL response envelope
|
|
-> pinned HTTP status/body matrix
|
|
-> data/errors state machine
|
|
-> operation data codec
|
|
-> mapper
|
|
```
|
|
|
|
top-level:
|
|
|
|
```text
|
|
GraphqlResponse
|
|
data?
|
|
errors?
|
|
extensions?
|
|
```
|
|
|
|
unknown top-level/extension behavior는 provider profile과 VD-24 unknown-field
|
|
정책을 따른다. response body, error message, path와 extensions를 log에 복사하지
|
|
않는다.
|
|
|
|
### 9. Data/error state machine
|
|
|
|
다음 순서로 배타적으로 처리한다.
|
|
|
|
1. network/final URL/unsupported media/body limit 실패 또는 legacy
|
|
`application/json` non-2xx:
|
|
transport 또는 media/limit failure, data/cache write 0.
|
|
2. `application/graphql-response+json`은 profile이 허용한 status 전체에서,
|
|
legacy `application/json`은 profile-admitted 2xx에서만 bounded parse한다.
|
|
top-level response shape 불일치는 `GRAPHQL_ENVELOPE_MISMATCH`.
|
|
3. `errors` key가 있으면 non-empty list여야 한다. `errors=[]`는 항상
|
|
`GRAPHQL_ENVELOPE_MISMATCH`다.
|
|
4. selected GraphQL-over-HTTP revision의 status/body matrix가 맞지 않으면
|
|
`GRAPHQL_HTTP_PROFILE_MISMATCH`다.
|
|
5. `data` key 존재 + non-null, `errors` 없음:
|
|
data codec → mapper → generation fence → success.
|
|
6. `data` 없음/null, non-empty `errors`:
|
|
safe error mapping; success/cache write 0.
|
|
7. non-null `data`와 non-empty `errors` 동시:
|
|
operation `partialDataPolicy` 적용.
|
|
8. `data` 없음/null이고 errors도 없음:
|
|
contract mismatch.
|
|
|
|
### 10. Error projection
|
|
|
|
GraphQL error는 untrusted다.
|
|
|
|
```text
|
|
GraphqlError
|
|
message
|
|
locations
|
|
path
|
|
extensions
|
|
```
|
|
|
|
application에 허용:
|
|
|
|
- operation error profile이 allowlist한 `extensions.code`
|
|
- bounded typed validation field issue
|
|
- effect certainty/conflict category
|
|
- bounded server request/trace ID projection
|
|
|
|
금지:
|
|
|
|
- raw `message`
|
|
- source location
|
|
- path actual value
|
|
- arbitrary extensions
|
|
- resolver/service/stack/database detail
|
|
|
|
error count, path segment/count/string와 extensions decoded byte cap을 적용한다.
|
|
unknown code는 generic closed failure다. backend `retryable` boolean은 retry
|
|
authority가 아니다.
|
|
|
|
common mapping 예:
|
|
|
|
| safe GraphQL category | AppFailure |
|
|
| --- | --- |
|
|
| unauthenticated | `AUTH_REQUIRED` |
|
|
| forbidden | `FORBIDDEN` |
|
|
| not found | `NOT_FOUND` 또는 existence-hiding policy |
|
|
| validation | `VALIDATION_REJECTED` |
|
|
| conflict/precondition | `CONFLICT` 또는 typed precondition |
|
|
| rate limited | `RATE_LIMITED` |
|
|
| internal/unavailable | `SERVER_FAILURE` |
|
|
| unknown | `UNKNOWN_CLIENT_FAILURE` 또는 contract failure |
|
|
|
|
### 11. Partial data
|
|
|
|
default:
|
|
|
|
```text
|
|
partialDataPolicy = REJECT
|
|
```
|
|
|
|
query에만 다음 explicit profile을 허용할 수 있다.
|
|
|
|
```text
|
|
ALLOW_TYPED_PARTIAL
|
|
requiredCompletePaths
|
|
optionalPartialPaths
|
|
errorCodeAllowlist
|
|
completenessSchemaId
|
|
staleVisibilityPolicy
|
|
```
|
|
|
|
조건:
|
|
|
|
- data codec이 missing/null path를 정확히 표현
|
|
- mapper가 completeness를 application result로 투영
|
|
- UI가 complete success와 partial-degraded를 구분
|
|
- partial value/result size ceiling
|
|
- authorization/error path를 숨기며 unsafe field를 사용하지 않음
|
|
- previous complete cache와 field 단위로 임의 merge하지 않음
|
|
|
|
mutation은 errors가 있으면 partial success data를 ordinary command success로
|
|
cache하지 않는다. backend가 effect certainty/receipt를 제공해야
|
|
`COMMITTED | NOT_APPLIED | UNKNOWN`을 판단한다. error가 있다는 이유만으로
|
|
optimistic layer 전체를 즉시 rollback해 다른 commit을 덮지 않는다.
|
|
|
|
### 12. Null bubbling
|
|
|
|
GraphQL nullability propagation은 application null 의미와 다르다.
|
|
|
|
- nullable field, error-caused null과 absent partial field를 data/error state
|
|
machine이 함께 해석
|
|
- generated type의 `T | null`만으로 cause를 추측하지 않음
|
|
- operation data codec/mapper가 approved partial path와 error code를 결합
|
|
- required root/aggregate null은 default failure
|
|
- unauthorized field null을 stale previous field로 자동 채우지 않음
|
|
|
|
### 13. Cache identity
|
|
|
|
VD-25 TanStack Query가 기본 sole owner다.
|
|
|
|
- query key는 semantic operation input + VD-13 scope
|
|
- persisted operation ID/hash/document를 key에 넣지 않음
|
|
- GraphQL data/envelope/generated type을 cache하지 않음
|
|
- mapped bounded application projection만 cache
|
|
- schema/mapper meaning change는 query/release epoch invalidation
|
|
- GraphQL client library cache는 disabled/`no-cache`
|
|
|
|
normalized cache가 필요하면 separate ADR:
|
|
|
|
- key fields/`__typename`
|
|
- fragment completeness
|
|
- pagination merge
|
|
- optimistic layers
|
|
- eviction/gc/logout/scope
|
|
- persistence/SSR
|
|
- TanStack replacement/removal
|
|
|
|
dual cache는 금지한다.
|
|
|
|
### 14. Batching
|
|
|
|
현재 `NOT_SELECTED`.
|
|
|
|
`@defer`/`@stream`은 한 GraphQL HTTP operation의 finite incremental response다.
|
|
장기 subscription이나 unsolicited realtime event가 아니며, reconnect/resume
|
|
owner를 realtime runtime에 넘기지 않는다.
|
|
|
|
선택 조건:
|
|
|
|
- 같은 endpoint/auth/scope
|
|
- query only
|
|
- same credentials/CSRF policy
|
|
- max operation count
|
|
- total variables/request bytes
|
|
- total cost/depth
|
|
- per-operation deadline/result/error/observation 보존
|
|
- one operation cancel/failure가 다른 operation semantics를 바꾸지 않음
|
|
|
|
금지:
|
|
|
|
- mutation 포함
|
|
- query+mutation mixed batch
|
|
- 서로 다른 account/session
|
|
- batching으로 idempotency/retry owner 합치기
|
|
- one HTTP result를 one query cache value로 저장
|
|
|
|
batch transport failure와 per-operation GraphQL failure를 분리한다.
|
|
|
|
### 15. Incremental `@defer`/`@stream`
|
|
|
|
현재 `NOT_SELECTED`.
|
|
|
|
선택 시 별도 profile:
|
|
|
|
- exact incremental-delivery draft/provider revision
|
|
- exact `Accept`, response `Content-Type`와 boundary/version parameter
|
|
- exact `multipart/mixed` media/boundary parser
|
|
- total bytes/parts/depth/patch count
|
|
- initial/subsequent/terminal payload discriminant와 completion grammar
|
|
- operation-owned ID/label/path allowlist와 path progression
|
|
- patch/data/items/errors/extensions runtime schema
|
|
- part별 및 cumulative error/extension count/byte ceiling
|
|
- duplicate/out-of-order/missing path
|
|
- terminal marker
|
|
- idle/total deadline
|
|
- backpressure/cancel/reader cleanup
|
|
- proxy/CDN buffering conformance
|
|
|
|
cache:
|
|
|
|
- staging projection에 immutable patch 적용
|
|
- terminal integrity/completeness 뒤 atomic commit
|
|
- 또는 UI가 explicit progressive state를 소유
|
|
- existing cached object를 in-place mutate하지 않음
|
|
- truncated stream을 complete success로 cache하지 않음
|
|
|
|
Chromium/Firefox/WebKit과 actual proxy 증거 없이는 traffic promotion 금지다.
|
|
exact protocol revision/profile이 없으면 registry composition 자체를 거절한다.
|
|
|
|
### 16. Subscription
|
|
|
|
GraphQL HTTP query adapter에 subscription을 넣지 않는다. 현재 `NOT_SELECTED`.
|
|
|
|
선택 시 transport-specific registered GraphQL subscription capability와
|
|
feature-owned `FeatureEventInput`이 필요하다. 범용 `RealtimePort`를 만들지
|
|
않는다.
|
|
|
|
```text
|
|
GraphqlSubscriptionCapability
|
|
subscribe(registered subscription, validated variables, signal)
|
|
-> AsyncIterable<Result<MappedEvent>>
|
|
-> unsubscribe()
|
|
```
|
|
|
|
선택된 WebSocket/SSE subprotocol adapter가 frame, media, auth, reconnect/resume를
|
|
소유하고 GraphQL event schema와 pure mapper를 통과한 event만
|
|
`FeatureEventInput` 또는 invalidation bridge로 전달한다. backend contract가
|
|
명시적으로 같은 의미를 채택하지 않는 한 GraphQL payload를
|
|
`REALTIME_EVENT_V1`로 강제하거나 다시 감싸지 않는다.
|
|
|
|
backend 계약:
|
|
|
|
- exact WebSocket/SSE protocol/version
|
|
- auth attach/refresh/revoke
|
|
- heartbeat/idle timeout
|
|
- reconnect/backoff
|
|
- sequence/duplicate/gap/resume cursor
|
|
- bounded queue/overflow
|
|
- logout/route unmount unsubscribe
|
|
|
|
event는 invalidation hint 또는 registered bounded reducer를 통해 server-state를
|
|
갱신한다. raw event history를 Query cache에 무한 적재하지 않는다.
|
|
|
|
### 17. Authorization, CSRF와 DoS
|
|
|
|
- BFF/router가 field/resource authorization을 매 request에 수행
|
|
- persisted allowlist는 authorization이 아님
|
|
- cookie mutation은 POST + exact Origin/Fetch Metadata + approved CSRF proof
|
|
- SameSite/custom header/preflight 단일 요소만 방어라고 주장하지 않음
|
|
- cross-origin credential wildcard 금지
|
|
- server에서 depth, aliases, fragments, variables/list/page/field cost, total
|
|
execution와 response bytes 강제
|
|
- frontend ceiling은 server DoS 방어를 대체하지 않음
|
|
- introspection off는 field authorization/cost control 대체 아님
|
|
- persisted operation manifest와 field authorization change를 coherent rollout
|
|
|
|
### 18. Backend/router 계약
|
|
|
|
provider가 제공:
|
|
|
|
- immutable schema artifact/provenance
|
|
- persisted operation registration/lookup
|
|
- exact operation hash/schema digest binding
|
|
- N/N-1 manifest window와 retirement
|
|
- cost/depth/alias/list/response budget enforcement
|
|
- stable safe error code vocabulary
|
|
- partial/null/effect certainty semantics
|
|
- idempotency/conflict/revision
|
|
- auth/CSRF/CORS
|
|
- request/trace projection
|
|
- kill switch와 per-operation traffic
|
|
|
|
frontend manifest echo만으로 등록/authorization을 승인하지 않는다. router가
|
|
server-owned manifest에서 operation binding을 재계산한다.
|
|
|
|
### 19. Observability
|
|
|
|
허용:
|
|
|
|
- semantic operation ID/persisted profile ID
|
|
- schema/manifest compatibility outcome
|
|
- full/partial/rejected/transport outcome
|
|
- safe GraphQL error category
|
|
- cost/depth/variables/response/error/part count bucket
|
|
- duration/deadline/retry/auth recovery bucket
|
|
- cache hit/stale/admission outcome
|
|
|
|
금지:
|
|
|
|
- document/hash actual value
|
|
- variables/data
|
|
- raw error message/path/extensions
|
|
- field/resolver name high-cardinality label
|
|
- account/resource/cursor/revision
|
|
|
|
server가 resolver-level telemetry를 소유한다. browser가 raw field trace를 수집하지
|
|
않는다.
|
|
|
|
### 20. Testing
|
|
|
|
build/contract:
|
|
|
|
- schema source provenance/digest
|
|
- schema lint/breaking/deprecation budget
|
|
- named operation validation
|
|
- canonical hash/manifest determinism
|
|
- clean codegen diff
|
|
- generated import boundary
|
|
- variables/data codec parity
|
|
- N/N-1 persisted manifest and retirement
|
|
|
|
runtime:
|
|
|
|
- unknown/hash mismatch, full-document fallback 0
|
|
- variables depth/node/string/list/byte cap
|
|
- GraphQL HTTP revision/media/status-body matrix와 legacy intermediary body
|
|
- HTTP/media/body cap
|
|
- all data/errors state branches
|
|
- empty errors와 null/absent data matrix
|
|
- error count/path/extensions cap/redaction
|
|
- null bubbling
|
|
- partial allowed/rejected/completeness
|
|
- timeout/cancel/retry/auth recovery
|
|
- mutation effect certainty/idempotency
|
|
- scope/generation late result
|
|
- cache admission/write 0 on failure
|
|
|
|
optional:
|
|
|
|
- batching mixed/mutation/limit rejection
|
|
- multipart boundary/truncated/duplicate/out-of-order/terminal
|
|
- subscription ordering/reconnect/resume/logout
|
|
|
|
provider/browser:
|
|
|
|
- actual BFF/router allowlist/cost/auth/CSRF/CORS
|
|
- manifest rollout/retirement
|
|
- proxy/CDN media/body behavior
|
|
- Chromium/Firefox/WebKit for selected incremental/subscription capability
|
|
|
|
### 21. Rollout
|
|
|
|
1. product owner가 GraphQL이 필요한 bounded operation family를 승인한다.
|
|
2. schema/router/manifest owner와 endpoint/auth/cost/error contract를 확정한다.
|
|
3. provider-neutral codec/adapter/fake를 구현한다.
|
|
4. generated source, boundary mapper와 TanStack query definition을 연결한다.
|
|
5. REST current read와 GraphQL shadow read를 비교하되 shadow result는 UI/cache에
|
|
쓰지 않는다.
|
|
6. actual router conformance를 통과한다.
|
|
7. `AVAILABLE_NOT_COMPOSED`에서 product composition behind
|
|
`TrafficAdmission=DISABLED`로 이동한다.
|
|
8. read-only internal canary 뒤 selected operation만 traffic을 올린다.
|
|
9. mutation은 idempotency/effect certainty provider evidence 뒤 별도 canary한다.
|
|
10. batching/incremental/subscription은 계속 `NOT_SELECTED` 또는 독립 gate다.
|
|
|
|
rollback:
|
|
|
|
- 신규 GraphQL operation admission 중지
|
|
- in-flight query cancel, mutation effect reconcile
|
|
- current scope GraphQL-mapped query cache clear
|
|
- coherent frontend/schema/manifest/router rollback
|
|
- approved REST read fallback이 있으면 새 logical read로 전환
|
|
- arbitrary/full-document fallback 금지
|
|
|
|
### 22. Removal
|
|
|
|
1. operation traffic/registration retirement 시작
|
|
2. query/subscription cancel과 mutation reconcile
|
|
3. Query cache/invalidation listener clear
|
|
4. operation/codec/mapper/query profile 제거
|
|
5. generated files, GraphQL runtime/codegen dependencies 제거
|
|
6. schema/operation manifest/config/endpoint 제거
|
|
7. router persisted entries는 N/N-1 window 뒤 제거
|
|
8. production module/dependency/SBOM/removal test 통과
|
|
|
|
## 규범 기준
|
|
|
|
- [GraphQL Specification, September 2025](https://spec.graphql.org/September2025/)
|
|
- [GraphQL over HTTP draft](https://graphql.github.io/graphql-over-http/draft/)
|
|
|
|
GraphQL-over-HTTP 문서는 현재 draft이므로 링크의 moving text를 production
|
|
profile로 쓰지 않고 위에서 결정한 revision/provider fixture로 고정한다.
|
|
|
|
## 완료 기준
|
|
|
|
- production에서 registered persisted operation 외 document가 전송되지 않는다.
|
|
- schema/operation/codegen/runtime codec/mapper manifest가 digest로 연결된다.
|
|
- variables/response/errors가 bounded runtime validation을 거친다.
|
|
- persisted envelope extension과 GraphQL-over-HTTP revision/media/status matrix가
|
|
actual router profile에 고정된다.
|
|
- data/errors/partial/null/effect certainty 상태가 배타적으로 닫힌다.
|
|
- auth/CSRF/cost/field authorization과 manifest N/N-1을 actual router에서 증명한다.
|
|
- GraphQL SDK normalized cache와 TanStack dual cache가 없다.
|
|
- query key/cache에 document/hash/envelope/generated DTO가 없다.
|
|
- batching/incremental/subscription은 선택 전 설치되지 않는다.
|
|
- kill switch, rollback과 dependency/manifest removal drill이 통과한다.
|