719 lines
30 KiB
Markdown
719 lines
30 KiB
Markdown
# Backend API와 Server State handoff contract
|
|
|
|
> **정본 안내 (non-authoritative for runtime capability decisions)**
|
|
>
|
|
> Runtime Config/boot, Fetch HTTP client, Router, Query/Mutation, realtime 공통 경계, Web Worker,
|
|
> Service Worker, offline command와 Background Sync의 구현 결정은
|
|
> [프론트엔드 런타임 Capability 저장소 정합형 구현 결정 폐쇄 상세 설계](./2026-07-30-frontend-runtime-capability-repository-aligned-implementation-closed-deep-design.md)가 정본이다.
|
|
> 본 문서의 해당 서술이 정본과 충돌하면 정본을 따른다.
|
|
|
|
|
|
- 상태: frontend handoff design accepted, backend implementation/evidence pending
|
|
- 기준일: 2026-07-28
|
|
- 대상: Web API/BFF, application service, persistence, identity, GraphQL router,
|
|
Connect/gRPC-Web gateway, Protobuf REST Gateway와 운영 owner
|
|
- frontend 기준:
|
|
[API contract, Schema, Mapper와 Server State](./api-contract-schema-mapper-and-server-state.md)
|
|
- browser Protobuf/gateway 기준:
|
|
[Protobuf browser transport와 REST Gateway](./protobuf-browser-transport-and-rest-gateway.md)
|
|
- 파일 전송 backend 기준:
|
|
[Server file capability infrastructure](./server-file-capability-infrastructure.md)
|
|
- 복구 절차:
|
|
[API contract와 server-state recovery](../operations/api-contract-and-server-state-recovery.md)
|
|
|
|
## 1. 문서의 경계
|
|
|
|
이 문서는 이 frontend template이 실제 제품 backend와 연결될 때 backend가
|
|
제공해야 하는 구조, wire contract, 상태 의미와 운영 증거를 정의한다. 특정
|
|
언어·framework·cloud 제품을 강제하지 않는다. Spring, Nest/Fastify, Go,
|
|
.NET 또는 다른 stack을 사용해도 아래 불변조건은 동일하다.
|
|
|
|
이 repository에는 backend source, database migration, identity provider,
|
|
GraphQL schema/router, protobuf descriptor, Connect/gRPC-Web runtime/proxy,
|
|
REST transcoder와 실제 provider evidence가 없다. 따라서 이 문서는 backend 구현
|
|
완료 증거가 아니다.
|
|
|
|
파일 업로드·다운로드, object storage, presigned URL, multipart와 Image CDN은
|
|
별도 server file 문서가 소유한다. 이 문서는 ordinary REST/GraphQL/Connect/
|
|
gRPC-Web application API, Protobuf REST Gateway와 frontend Server State 계약만
|
|
소유한다.
|
|
|
|
## 2. 권장 논리 구조
|
|
|
|
```text
|
|
Browser
|
|
-> CDN / reverse proxy / WAF
|
|
-> Browser-facing Web API or BFF
|
|
-> authentication + authorization
|
|
-> exact operation registry
|
|
-> request schema / byte / rate limit
|
|
-> REST controller
|
|
-> optional persisted GraphQL router
|
|
-> optional Connect browser RPC gateway
|
|
-> optional gRPC-Web gateway
|
|
-> optional Protobuf REST transcoder
|
|
-> application service
|
|
-> command transaction
|
|
-> query/read-model service
|
|
-> idempotency coordinator
|
|
-> revision/validator owner
|
|
-> outbox/event owner
|
|
-> primary database
|
|
-> idempotency store
|
|
-> read replica/read model
|
|
-> event broker when selected
|
|
```
|
|
|
|
Browser-facing contract owner와 내부 service contract owner를 분리한다.
|
|
|
|
- Browser API/BFF는 CORS, cookie/CSRF 또는 bearer, public DTO, envelope,
|
|
body ceiling, status/media와 redaction을 소유한다.
|
|
- Application service는 authorization 재검사, transaction, idempotency,
|
|
conflict, revision과 domain invariant를 소유한다.
|
|
- Persistence adapter는 SQL/NoSQL/Redis vendor type, row version과 cursor
|
|
implementation을 외부 DTO에 노출하지 않는다.
|
|
- GraphQL router, Connect/gRPC-Web gateway와 REST transcoder는 선택 adapter다.
|
|
다른 protocol로 임의 fallback하거나 frontend에 내부 service address를
|
|
노출하지 않는다.
|
|
|
|
작은 제품은 이 논리 모듈을 하나의 deployable로 구현할 수 있다. deployable을
|
|
나누는 것보다 transaction/idempotency/authorization owner가 하나로 명확한지가
|
|
우선이다.
|
|
|
|
## 3. 공통 contract artifact
|
|
|
|
Backend와 frontend release는 다음 bounded contract set을 공유한다.
|
|
|
|
```text
|
|
ApiContractSetV1
|
|
globalApiContractVersion
|
|
restArtifactId + digest
|
|
runtimeSchemaManifestId + digest
|
|
mapperSemanticManifestId + digest
|
|
errorVocabularyVersion
|
|
minimumFrontendVersion
|
|
minimumBackendVersion
|
|
effectiveAt
|
|
retirementEpoch | null
|
|
|
|
optional:
|
|
graphqlSchemaId + digest
|
|
persistedGraphqlManifestId + digest
|
|
protobufDescriptorId + digest
|
|
protobufSourceOrModuleId + digest
|
|
protobufCodegenProfileId
|
|
connectProviderProfileId
|
|
grpcWebProviderProfileId
|
|
protobufRestGatewayProfileId
|
|
httpRuleArtifactId + digest
|
|
protoJsonProfileId
|
|
gatewayOpenApiArtifactId + digest
|
|
```
|
|
|
|
최소 산출물:
|
|
|
|
- authenticated OpenAPI 또는 동등한 REST schema source
|
|
- exact status/media/envelope fixture
|
|
- stable error code vocabulary
|
|
- request/response byte와 collection ceiling
|
|
- scalar/date/null/enum 의미
|
|
- N/N-1 compatibility 결과
|
|
- backend build와 frontend release가 참조하는 immutable digest
|
|
|
|
runtime config의 version 문자열 일치만 compatibility 증거로 사용하지 않는다.
|
|
artifact digest가 없는 동안에는 실제 staging conformance fixture와 수동 승인
|
|
evidence가 필요하다.
|
|
|
|
## 4. 현재 reference REST 계약
|
|
|
|
현재 frontend에 실제 조립된 operation은 다음 세 개다.
|
|
|
|
| operation | request | success |
|
|
| --- | --- | --- |
|
|
| `LIST_REFERENCE_RESOURCES` | `GET /api/reference-resources?cursor&limit&tags` | `200 application/json` |
|
|
| `GET_REFERENCE_RESOURCE` | `GET /api/reference-resources/{resourceId}` | `200 application/json` |
|
|
| `CREATE_REFERENCE_RESOURCE` | `POST /api/reference-resources` | `200` 또는 `201 application/json` |
|
|
|
|
현재 list payload는 `ReferenceResource[]`다. `cursor` 입력이 존재하더라도
|
|
`CursorPage` 출력 계약은 아직 아니다. backend가 같은 operation에서 배열을
|
|
page object로 조용히 바꾸면 schema mismatch로 실패한다.
|
|
|
|
resource DTO:
|
|
|
|
```text
|
|
ReferenceResourceDtoV1
|
|
id: non-empty string, maximum 120 characters
|
|
name: non-empty string, maximum 240 characters
|
|
createdAt?: RFC 3339 date-time
|
|
```
|
|
|
|
create command:
|
|
|
|
```text
|
|
CreateReferenceResourceCommandV1
|
|
name: trimmed string, 1..120
|
|
note?: trimmed string, 0..500
|
|
```
|
|
|
|
Backend는 frontend validation을 신뢰하지 않고 동일하거나 더 좁은 validation과
|
|
authorization을 다시 수행한다.
|
|
|
|
### 4.1 JSON envelope
|
|
|
|
모든 현재 JSON success/failure는 다음 envelope를 사용한다.
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {},
|
|
"meta": {
|
|
"requestId": "server-request-id",
|
|
"traceId": "server-trace-id",
|
|
"correlationId": "client-correlation-id"
|
|
}
|
|
}
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": {
|
|
"code": "STABLE_MACHINE_CODE",
|
|
"category": "optional-safe-category",
|
|
"message": "optional non-sensitive copy",
|
|
"retryable": false,
|
|
"details": {}
|
|
},
|
|
"meta": {
|
|
"requestId": "server-request-id",
|
|
"traceId": "server-trace-id",
|
|
"correlationId": "client-correlation-id"
|
|
}
|
|
}
|
|
```
|
|
|
|
Envelope 최상위 unknown field는 현재 거절된다. ordinary resource DTO의 unknown
|
|
field는 frontend schema에서 strip되지만 additive compatibility는 contract
|
|
review와 fixture를 먼저 통과해야 한다.
|
|
|
|
`requestId`, `traceId`, `correlationId`는 각각 1..128 범위의 안전한 opaque
|
|
identifier다. credential, user data, cursor, validator와 database key를
|
|
identifier에 encode하지 않는다.
|
|
|
|
### 4.2 Status와 error
|
|
|
|
| HTTP status | 의미 |
|
|
| --- | --- |
|
|
| `400` | malformed request 또는 closed request contract 위반 |
|
|
| `401` | 인증 없음/만료. 이미 적용된 command를 401로 반환하지 않음 |
|
|
| `403` | authenticated principal에게 권한 없음 |
|
|
| `404` | authorization 정책상 공개 가능한 not-found |
|
|
| `409` | idempotency fingerprint, domain revision 또는 semantic conflict |
|
|
| `412` | selected conditional write의 `If-Match` precondition 실패 |
|
|
| `422` | field validation. bounded `details.issues[]`만 허용 |
|
|
| `429` | rate limit. 유효한 `Retry-After`와 operation 정책 제공 |
|
|
| `500/502/503/504` | server/provider failure. command effect certainty 별도 |
|
|
|
|
현재 frontend의 ordinary status mapper는 `412` 전용 처리를 아직 연결하지
|
|
않았다. conditional mutation을 선택할 때 frontend failure vocabulary와
|
|
transaction을 함께 승격해야 한다.
|
|
|
|
Backend `error.code`는 machine-readable stable code다. stack, SQL/vendor error,
|
|
raw validation value, authorization reason과 내부 service address를 반환하지
|
|
않는다.
|
|
|
|
## 5. 인증, CSRF와 CORS
|
|
|
|
현재 reference operation은 다음 profile로 조립돼 있다.
|
|
|
|
```text
|
|
auth = external bearer
|
|
Authorization: Bearer <credential>
|
|
fetch credentials = omit
|
|
CSRF profile = none
|
|
redirect = error
|
|
referrer policy = no-referrer
|
|
```
|
|
|
|
Backend/BFF는 bearer의 issuer, audience, signature algorithm, time claims와
|
|
revocation/session policy를 검증하고 operation별 authorization을 적용한다.
|
|
401과 403을 구분하며 frontend cache를 authorization authority로 사용하지 않는다.
|
|
|
|
쿠키 session으로 전환할 경우 같은 profile로 간주하지 않는다. 별도
|
|
`SAME_ORIGIN_COOKIE` profile에 다음을 함께 승인한다.
|
|
|
|
- `Secure`, `HttpOnly`, 명시적 `SameSite`와 host/path scope
|
|
- unsafe method의 CSRF token/header와 Origin/Sec-Fetch-Site 검증
|
|
- credentialed CORS에서 wildcard origin 금지
|
|
- login/logout/session rotation과 cache generation 전환
|
|
- session fixation, token rotation과 concurrent tab 동작
|
|
|
|
Cross-origin bearer provider 최소 CORS:
|
|
|
|
- exact allow-origin 목록과 bounded preflight cache
|
|
- `Authorization`, `Content-Type`, `Idempotency-Key`,
|
|
`X-Correlation-ID`, 향후 `If-None-Match`, `If-Match` 허용
|
|
- 필요한 경우 `ETag`, `Retry-After`, request/trace header만 expose
|
|
- redirect login page, HTML error body와 wildcard credential 금지
|
|
|
|
## 6. Command와 idempotency
|
|
|
|
`CREATE_REFERENCE_RESOURCE`는 keyed command다. frontend memory single-flight는
|
|
backend idempotency를 대체하지 않는다.
|
|
|
|
idempotency identity:
|
|
|
|
```text
|
|
principal/tenant
|
|
+ semantic operation ID and contract version
|
|
+ Idempotency-Key
|
|
+ canonical request fingerprint
|
|
```
|
|
|
|
권장 record:
|
|
|
|
```text
|
|
IdempotencyRecord
|
|
principalFingerprint
|
|
operationId
|
|
contractVersion
|
|
idempotencyKeyHash
|
|
requestFingerprint
|
|
state = IN_PROGRESS | COMMITTED | FAILED_SAFE | EFFECT_UNKNOWN
|
|
responseStatus
|
|
responseEnvelopeReference
|
|
resourceRevision | null
|
|
leaseOwner + leaseExpiry
|
|
retentionExpiry
|
|
createdAt + completedAt
|
|
```
|
|
|
|
불변조건:
|
|
|
|
- claim과 command transaction의 관계가 원자적이거나 crash reconciliation
|
|
가능해야 한다.
|
|
- 같은 key와 같은 fingerprint replay는 같은 authoritative receipt를 반환한다.
|
|
- 같은 key와 다른 fingerprint는 `409 IDEMPOTENCY_KEY_REUSED`다.
|
|
- concurrent replay는 하나만 실행하고 나머지는 같은 result를 기다리거나
|
|
bounded `IN_PROGRESS` 결과를 받는다.
|
|
- commit 뒤 response 유실은 새 resource를 만들지 않는다.
|
|
- `EFFECT_UNKNOWN`은 새 key로 자동 재시도하지 않고 status/reconcile endpoint로
|
|
확인한다.
|
|
- retention은 frontend retry/recovery 최대 window보다 길고 quota/abuse limit이
|
|
있다.
|
|
- key 원문과 request body를 log/metric label에 넣지 않는다.
|
|
|
|
Database unique constraint 또는 durable compare-and-set이 최종 중복 방지
|
|
authority여야 한다. process-local map/lock만 사용하지 않는다.
|
|
|
|
## 7. Cursor pagination과 snapshot
|
|
|
|
Backend가 pagination을 선택할 때 새 response schema/operation version으로 다음
|
|
contract를 제공한다.
|
|
|
|
```text
|
|
CursorPage<T>
|
|
items: T[]
|
|
nextCursor: opaque string | null
|
|
hasMore: boolean
|
|
snapshotToken: opaque string | null
|
|
```
|
|
|
|
필수 불변조건:
|
|
|
|
- `hasMore === (nextCursor !== null)`
|
|
- 동일 chain의 `snapshotToken`은 모든 page에서 동일
|
|
- cursor는 principal/tenant, filter, sort, contract version과 snapshot에 binding
|
|
- cursor는 opaque, 무결성 보호, 만료와 key rotation 정책 보유
|
|
- offset이 아니라 stable keyset ordering 사용
|
|
- total order의 마지막 tie-breaker는 immutable unique ID
|
|
- deleted/inserted row가 duplicate/gap을 만드는 의미를 snapshot 정책으로 결정
|
|
- empty page인데 `hasMore=true`인 sparse page 허용 여부를 operation profile에 고정
|
|
- cursor 최대 encoded byte, page size와 total scan/cost ceiling을 server도 강제
|
|
- invalid, expired, wrong-principal, wrong-filter cursor의 safe error code를 고정
|
|
|
|
권장 query ordering 예:
|
|
|
|
```text
|
|
ORDER BY created_at DESC, resource_id DESC
|
|
cursor payload = version + snapshot watermark + last(created_at, resource_id)
|
|
+ filter digest + principal/tenant binding + expiry
|
|
```
|
|
|
|
Cursor 원문은 log, trace, analytics와 frontend persistent storage에 넣지 않는다.
|
|
|
|
### 7.1 배열에서 page로의 migration
|
|
|
|
1. `ReferenceResourceListPagePayloadV2` schema와 새 operation/version을 추가한다.
|
|
2. backend가 N/N-1 동안 기존 배열과 page contract를 동시에 제공한다.
|
|
3. frontend가 cursor runtime을 새 bound query/infinite query에 연결한다.
|
|
4. loop/snapshot/ceiling/abort conformance를 staging에서 검증한다.
|
|
5. 새 operation을 canary한 뒤 기존 배열 operation을 retirement한다.
|
|
|
|
동일 media/status에서 payload shape만 바꾸는 in-place migration은 금지한다.
|
|
|
|
## 8. Conditional read와 revision/CAS
|
|
|
|
### 8.1 Read validator
|
|
|
|
Backend가 application-managed revalidation을 선택하면 exact mapped
|
|
representation마다 ETag를 제공한다.
|
|
|
|
```text
|
|
GET without validator
|
|
-> 200 + JSON envelope + ETag
|
|
|
|
GET with If-None-Match
|
|
-> representation unchanged: 304 + empty body
|
|
-> changed: 200 + JSON envelope + new ETag
|
|
```
|
|
|
|
불변조건:
|
|
|
|
- validator는 principal/tenant, authorization-visible representation,
|
|
response schema/mapper semantics와 encoding variant에 binding
|
|
- weak/strong 선택을 operation profile에 고정
|
|
- user-private response를 shared CDN/public cache에 저장하지 않음
|
|
- cross-origin이면 `ETag`를 expose하고 `If-None-Match`를 preflight 허용
|
|
- 304에는 JSON success envelope를 넣지 않음
|
|
- validator 원문을 log/metric/diagnostics에 넣지 않음
|
|
- `Vary`와 `Cache-Control` owner를 명확히 하고 browser HTTP cache와
|
|
TanStack/application revalidation이 서로 다른 value owner가 되지 않게 함
|
|
|
|
Frontend는 validator와 mapped cache value의 scope, query identity,
|
|
representation version과 cache revision이 모두 일치할 때만 304를 success로
|
|
받는다. cache value가 없으면 unconditional refetch 또는 safe failure로 닫는다.
|
|
|
|
### 8.2 Conditional command
|
|
|
|
수정/삭제 command가 선택되면 DTO에 opaque domain `revision`을 추가하고:
|
|
|
|
```text
|
|
If-Match: "<revision validator>"
|
|
```
|
|
|
|
를 요구한다. 일치하지 않으면 `412` 또는 승인된 `409` contract 하나만
|
|
사용한다. frontend optimistic layer의 commit/rollback은 backend revision
|
|
authority를 대체하지 않는다.
|
|
|
|
## 9. Optimistic mutation을 위한 backend 의미
|
|
|
|
Frontend ordered optimistic layer runtime은 구현돼 있지만 제품 operation에
|
|
연결하려면 backend가 다음을 결정해야 한다.
|
|
|
|
- resource/list membership을 결정하는 canonical filter와 sort
|
|
- command가 생성/수정/삭제하는 stable identity
|
|
- server-assigned ID와 client correlation의 reconcile 방법
|
|
- authoritative resource/list revision
|
|
- conflict status와 stable error code
|
|
- commit response가 complete resource인지 receipt인지
|
|
- effect certainty와 idempotency status/reconcile endpoint
|
|
- event/outbox가 있을 때 sequence/gap/snapshot reset 의미
|
|
|
|
Create가 server-assigned ID를 사용하는 경우 temporary UI ID를 backend ID로
|
|
원자적으로 교체하고 관련 detail/list key를 reconcile하는 정책이 필요하다.
|
|
이 의미 없이 generic optimistic append를 기본 활성화하지 않는다.
|
|
|
|
## 10. Database와 application service baseline
|
|
|
|
구현 예시는 다음 논리 table/constraint를 만족해야 한다.
|
|
|
|
```text
|
|
reference_resource
|
|
tenant_id
|
|
resource_id
|
|
display_name
|
|
note
|
|
revision
|
|
created_at
|
|
updated_at
|
|
deleted_at | null
|
|
unique(tenant_id, resource_id)
|
|
|
|
idempotency_record
|
|
principal/tenant fingerprint
|
|
operation + contract version
|
|
key hash
|
|
request fingerprint
|
|
state + receipt
|
|
lease/retention timestamps
|
|
unique(principal/tenant, operation, contract version, key hash)
|
|
|
|
outbox_event when selected
|
|
aggregate identity + revision
|
|
event type/version
|
|
sequence
|
|
payload reference or bounded safe projection
|
|
publication state
|
|
```
|
|
|
|
Application service transaction은 authorization scope와 tenant predicate를
|
|
모든 read/write에 적용하고, resource mutation과 revision/outbox 기록을 같은
|
|
transaction boundary에 둔다. cache/replica lag를 고려해 command 직후 read
|
|
consistency와 invalidation owner를 선언한다.
|
|
|
|
## 11. GraphQL 선택 시 추가 구조
|
|
|
|
GraphQL은 제품 operation이 REST보다 aggregation 이점을 실제로 가질 때만
|
|
선택한다.
|
|
|
|
```text
|
|
Browser
|
|
-> persisted-operation endpoint
|
|
-> manifest allowlist
|
|
-> auth/CSRF/rate/cost/depth/alias enforcement
|
|
-> GraphQL router
|
|
-> application services/loaders
|
|
```
|
|
|
|
Backend handoff:
|
|
|
|
- authenticated immutable schema artifact와 digest
|
|
- named operation source와 persisted ID/hash manifest
|
|
- variables/result runtime fixtures
|
|
- selected GraphQL-over-HTTP revision과 exact media/status profile
|
|
- partial data policy와 safe error extension vocabulary
|
|
- field/row authorization, cost/depth/alias/list ceiling
|
|
- N/N-1 router/frontend manifest rollout과 retirement
|
|
|
|
Production endpoint는 arbitrary document와 persisted miss 후 full-document
|
|
fallback을 받지 않는다. normalized frontend entity cache는 별도 제품 선택이다.
|
|
|
|
## 12. gRPC-Web 선택 시 추가 구조
|
|
|
|
gRPC-Web은 browser-facing gateway/proxy가 실제 선택된 unary 또는 bounded
|
|
server-stream operation에만 사용한다.
|
|
|
|
```text
|
|
Browser
|
|
-> same-origin BFF/Envoy/gRPC-Web gateway
|
|
-> exact service/method allowlist
|
|
-> frame/message/deadline/status/trailer enforcement
|
|
-> internal gRPC application service
|
|
```
|
|
|
|
Backend handoff:
|
|
|
|
- authenticated proto source와 immutable descriptor digest
|
|
- Buf/protoc lint/breaking 및 deterministic generation evidence
|
|
- exact service/method/rpc-kind allowlist
|
|
- selected gRPC-Web runtime kind, client API와 binary/JSON/text/wire revision;
|
|
official XHR와 Connect-Web Fetch profile을 분리
|
|
- proxy CORS, content-type, terminal status/trailer behavior
|
|
- Envoy를 선택하면 exact version/config digest, filter order, upstream HTTP/2,
|
|
route/idle/max-stream timeout, timeout offset와 buffering/flush
|
|
- message/frame/count/queue/idle/total budget
|
|
- server-stream sequence, gap, resume와 snapshot reset protocol
|
|
- actual browser/proxy conformance
|
|
|
|
client streaming과 bidirectional streaming은 common gRPC-Web browser contract로
|
|
간주하지 않는다. upload는 REST transfer, duplex는 별도 protocol을 선택한다.
|
|
|
|
## 13. Connect-Web/Connect 선택 시 추가 구조
|
|
|
|
Connect는 Protobuf-first backend의 selected unary 또는 bounded server-stream
|
|
operation에만 사용한다. Connect protocol과 Connect-Web의 gRPC-Web transport는
|
|
서로 다른 provider row다.
|
|
|
|
```text
|
|
Browser Connect-Web adapter
|
|
-> same-origin BFF 또는 exact cross-origin Connect endpoint
|
|
-> auth/CSRF/CORS + service/method allowlist
|
|
-> Connect protocol handler
|
|
-> application service
|
|
```
|
|
|
|
Backend handoff:
|
|
|
|
- authenticated proto/Buf source, descriptor와 generated-service digest
|
|
- exact Connect-Web/client runtime과 server/gateway version
|
|
- protocol revision, JSON/binary encoding, POST 또는 approved GET
|
|
- unary HTTP/error profile 또는 stream EndStream terminal profile
|
|
- request/response/envelope/message/count/queue byte ceiling
|
|
- unary/stream compression capability; stock browser stream은 identity-only
|
|
- total/idle timeout, browser abort→server context→downstream cancellation 전파
|
|
- exact CORS allow/expose/preflight와 auth/CSRF profile
|
|
- actual Chromium/Firefox/WebKit와 selected proxy/server conformance
|
|
|
|
GET은 descriptor `NO_SIDE_EFFECTS`, non-sensitive bounded input, URL/cache key,
|
|
`Vary`와 credential policy가 모두 승인된 unary에만 허용한다. browser
|
|
client-streaming/bidi는 Connect protocol 자체 기능과 별개로 `PLATFORM_LIMITED`다.
|
|
|
|
## 14. Protobuf REST Gateway 선택 시 추가 구조
|
|
|
|
한 route는 `CURATED_BFF | GRPC_GATEWAY | ENVOY_TRANSCODER` 중 하나만 소유한다.
|
|
현재 reference REST의 envelope와 `200|201`, 향후 `204/304/412` 의미를 유지하는
|
|
기본 선택은 curated BFF다.
|
|
|
|
Direct gateway는 ProtoJSON/HttpRule/status/error 자체를 새 public contract로
|
|
승인한 unary operation에서만 선택한다. Backend handoff:
|
|
|
|
- `.proto` annotation 또는 precedence가 고정된 service config의 immutable source
|
|
- descriptor/Buf image, canonical HttpRule route manifest와 digest
|
|
- pinned gateway/runtime/generator/plugin과 generated OpenAPI artifact
|
|
- ProtoJSON name/default/enum/int64/bytes/null/presence/unknown-field profile
|
|
- exact method/path/query/body/response-body/additional-binding와 path escaping
|
|
- safe status/error/header mapping과 raw `google.rpc.Status` detail redaction
|
|
- CORS/auth/CSRF, body/header/query ceiling와 rate limit
|
|
- browser abort/deadline의 upstream gRPC/application work 전파
|
|
- N/N-1 route/OpenAPI/runtime conformance와 coherent rollback
|
|
|
|
Gateway는 durable idempotency, pagination snapshot, ETag/HTTP conditional,
|
|
product envelope, authorization와 file transfer semantics를 자동 구현하지 않는다.
|
|
필요한 operation은 application service와 BFF가 계속 소유한다. generated
|
|
REST streaming은 별도 framing/terminal/cache ADR 없이는 `NOT_SELECTED`다.
|
|
|
|
## 15. Invalidation과 realtime
|
|
|
|
현재 frontend cross-tab invalidation은 같은 browser origin 안의 opaque
|
|
invalidate-only hint다. backend event delivery를 의미하지 않는다.
|
|
|
|
Backend-driven invalidation/realtime을 선택하면:
|
|
|
|
- transactional outbox 또는 동등한 durable publication
|
|
- principal/tenant authorization을 통과한 event projection
|
|
- event type/version, aggregate revision, sequence와 dedupe identity
|
|
- reconnect cursor, gap detection과 snapshot reset
|
|
- retention, replay ceiling과 slow-consumer policy
|
|
|
|
를 제공해야 한다. event payload를 authoritative resource snapshot으로 쓸지
|
|
query invalidate hint로만 쓸지 operation별 reducer contract가 필요하다.
|
|
|
|
## 16. Rate limit, deadline와 retry
|
|
|
|
- backend deadline은 frontend total deadline보다 짧거나 cancellation을 전파할 수
|
|
있어야 한다.
|
|
- disconnect/cancel 뒤 불필요한 query 작업은 중단한다.
|
|
- keyed command는 disconnect가 transaction rollback을 보장하지 않으므로
|
|
idempotency receipt로 effect를 판정한다.
|
|
- `Retry-After`는 selected status에서만 bounded delta/date 형식으로 제공한다.
|
|
- retry-safe read와 keyed command를 구분한다.
|
|
- proxy, BFF와 service retry가 겹쳐 retry amplification을 만들지 않게 한 owner만
|
|
재시도한다.
|
|
- rate limit key는 principal/tenant/operation과 abuse policy에 binding하며 raw
|
|
credential/IP를 metric label에 넣지 않는다.
|
|
|
|
## 17. Observability와 privacy
|
|
|
|
허용되는 공통 dimension:
|
|
|
|
```text
|
|
operation ID
|
|
contract/profile version
|
|
status group / safe error code
|
|
attempt bucket
|
|
duration bucket
|
|
provider/runtime health
|
|
traffic admission stage
|
|
```
|
|
|
|
금지:
|
|
|
|
- Authorization, cookie, CSRF와 idempotency key
|
|
- request/response body와 validation value
|
|
- URL query, cursor, snapshot, ETag/revision
|
|
- GraphQL variables/path/raw error/extensions
|
|
- protobuf bytes, metadata와 trailer 원문
|
|
- user ID/email/file name을 metric label이나 trace attribute로 사용
|
|
|
|
Request ID와 trace ID는 browser에 반환할 수 있지만 credential 역할을 하지 않으며
|
|
추측 가능한 database primary key를 포함하지 않는다.
|
|
|
|
필수 SLO/alert 후보:
|
|
|
|
- operation availability와 latency
|
|
- 401/403/409/412/422/429 및 5xx rate
|
|
- schema/mapper/contract mismatch
|
|
- idempotency in-progress age, collision과 unknown effect
|
|
- cursor invalid/expired/loop-equivalent server detection
|
|
- conditional hit/miss와 invalid 304
|
|
- GraphQL persisted miss/cost reject
|
|
- Connect missing/duplicate EndStream, whole-body/queue overflow와 compression mismatch
|
|
- gRPC-Web missing terminal status, frame/idle/queue overflow
|
|
- REST Gateway route/OpenAPI/runtime rewrite drift와 cancel propagation loss
|
|
|
|
## 18. 배포, compatibility와 rollback
|
|
|
|
권장 순서:
|
|
|
|
1. contract artifact와 compatibility diff를 생성한다.
|
|
2. backend가 N/N-1 fixture를 통과한 상태로 먼저 배포한다.
|
|
3. frontend operation은 traffic disabled 상태에서 staging conformance를 실행한다.
|
|
4. read-only shadow/canary 뒤 query traffic을 올린다.
|
|
5. keyed command는 idempotency/reconcile fault injection 뒤 별도 canary한다.
|
|
6. pagination, conditional, optimistic, GraphQL, Connect, gRPC-Web과 REST
|
|
Gateway는 각각 독립 gate로 승격한다.
|
|
7. provider/browser/operations evidence가 완료된 operation만 enabled한다.
|
|
|
|
Rollback은 frontend/backend/contract artifact를 coherent set으로 되돌린다.
|
|
unknown-effect command를 다른 protocol이나 새 idempotency key로 replay하지 않는다.
|
|
Backend가 old contract를 제거하는 시점은 실제 frontend support window와 cache/CDN
|
|
retention 뒤다.
|
|
|
|
## 19. Conformance와 fault-injection matrix
|
|
|
|
Backend 완료 판정에는 unit test 외에 actual staging provider evidence가 필요하다.
|
|
|
|
| 범위 | 필수 증거 |
|
|
| --- | --- |
|
|
| REST | exact path/query/body, media/status/envelope, max body, malformed/truncated JSON |
|
|
| Auth | missing/expired credential, 401/403, rotation, cross-origin preflight |
|
|
| Command | concurrent same-key replay, fingerprint mismatch, commit 뒤 response loss |
|
|
| Cursor | filter/sort binding, expiry, snapshot stability, loop/gap/duplicate 방지 |
|
|
| Conditional | 200→304, cache-missing 304 방지, representation change, 412 |
|
|
| Schema | additive/breaking/null/enum/time/number fixtures와 N/N-1 |
|
|
| GraphQL | persisted hit/miss/hash mismatch, partial, cost/depth, router rollout |
|
|
| Connect | JSON/binary unary, GET restriction, EndStream, body/message cap, compression, cancel/deadline와 CORS |
|
|
| gRPC-Web | proxy media/status/trailer, oversized frame, cancel, idle, gap/resume |
|
|
| REST Gateway | HttpRule path/query/body, ProtoJSON, OpenAPI/status/error rewrite, abort propagation과 N/N-1 |
|
|
| Operations | deadline/retry amplification, rate limit, kill switch, coherent rollback |
|
|
|
|
## 20. Backend handoff checklist
|
|
|
|
- [ ] Browser-facing API/BFF owner와 on-call이 정해졌다.
|
|
- [ ] reference REST exact endpoint/envelope/status/media fixture가 있다.
|
|
- [ ] bearer 또는 cookie+CSRF 중 하나의 실제 profile과 CORS evidence가 있다.
|
|
- [ ] stable error vocabulary와 redaction contract가 있다.
|
|
- [ ] keyed command idempotency store, TTL, receipt와 reconcile이 있다.
|
|
- [ ] CursorPage를 선택했다면 opaque cursor/snapshot contract가 있다.
|
|
- [ ] conditional을 선택했다면 ETag/304/412와 cache owner가 있다.
|
|
- [ ] optimistic을 선택했다면 identity/membership/revision/conflict 의미가 있다.
|
|
- [ ] OpenAPI/runtime schema/mapper semantic artifact와 digest가 release에 binding됐다.
|
|
- [ ] GraphQL을 선택했다면 schema/persisted manifest/router evidence가 있다.
|
|
- [ ] Connect를 선택했다면 descriptor/runtime/server/browser evidence가 있다.
|
|
- [ ] gRPC-Web을 선택했다면 descriptor/proxy/browser evidence가 있다.
|
|
- [ ] REST Gateway를 선택했다면 kind/HttpRule/ProtoJSON/OpenAPI와 runtime
|
|
conformance evidence가 있다.
|
|
- [ ] staging conformance, fault injection, canary, kill switch와 rollback drill이
|
|
통과했다.
|
|
|
|
## 21. Frontend 완료 경계
|
|
|
|
Backend 구현과 별개로 현재 frontend 상태를 다음처럼 해석한다.
|
|
|
|
| 범위 | 현재 상태 | 남은 owner |
|
|
| --- | --- | --- |
|
|
| REST path/provider/auth/deadline/bounded JSON | `COMPOSED` | actual provider conformance는 backend/operations |
|
|
| runtime schema와 mapper registry | `COMPOSED` | artifact digest/source provenance는 backend contract source + frontend/platform |
|
|
| session generation과 query identity | `COMPOSED` | account identity projection은 identity integration + frontend |
|
|
| Cursor runtime | `AVAILABLE_NOT_COMPOSED` | CursorPage backend 계약 후 frontend query binding |
|
|
| conditional validator store | `AVAILABLE_NOT_COMPOSED` | ETag/304/412 backend 계약 후 frontend HTTP/cache transaction |
|
|
| ordered optimistic layer | `AVAILABLE_NOT_COMPOSED` | product membership/revision 승인 후 frontend mutation definition |
|
|
| GraphQL adapter | `DESIGNED_NOT_IMPLEMENTED` | product/backend 선택 뒤 frontend adapter/codegen |
|
|
| Browser RPC V3 공통 계약/coordinator | `AVAILABLE_NOT_COMPOSED` | selected descriptor/generated client와 protocol transport 확정 뒤 frontend provider adapter |
|
|
| Protobuf schema/codegen | `DESIGNED_NOT_IMPLEMENTED` | authenticated backend contract source 선택 뒤 pinned frontend generation |
|
|
| Connect-Web adapter | `DESIGNED_NOT_IMPLEMENTED` | product/backend/server 선택 뒤 frontend adapter/codegen |
|
|
| gRPC-Web adapter | `DESIGNED_NOT_IMPLEMENTED` | product/backend/proxy 선택 뒤 frontend adapter/codegen |
|
|
| Protobuf REST Gateway | `NOT_SELECTED` | gateway kind와 public HTTP contract 승인 뒤 REST adapter binding |
|
|
| persisted query/offline command | `NOT_SELECTED` | 별도 product ADR와 backend durability 계약 |
|
|
|
|
따라서 “backend만 구현하면 frontend가 아무 변경 없이 모든 capability를 자동
|
|
사용한다”는 의미는 아니다. 현재 선택된 REST reference vertical의 공통 frontend
|
|
기반은 완료됐지만, backend contract가 확정되면 Cursor/conditional/optimistic의
|
|
마지막 composition과 schema/mapper 변경이 frontend에 남는다. GraphQL,
|
|
Connect/gRPC-Web과 Protobuf REST Gateway는 제품이 선택되지 않았다. 공통 Browser
|
|
RPC operation/profile registry, application port와 lifecycle coordinator는
|
|
구현했지만, wire별 generated client/decoder/provider binding은 아직 구현하지
|
|
않았다. 따라서 backend contract가 정해져도 frontend provider adapter와
|
|
composition 작업은 명시적으로 남는다.
|