42 KiB
VD-30: Protobuf contract와 REST transcoding gateway
- 상태: Accepted design — schema/codegen과 provider runtime implementation pending
- 결정일: 2026-07-28
- Protobuf schema/codegen reference governance:
DESIGNED_NOT_IMPLEMENTED - provider-neutral REST transcoding reference:
DESIGNED_NOT_IMPLEMENTED - product REST Gateway composition:
NOT_SELECTED - 현재 installed REST reference의 기본 public adapter: curated BFF
- direct REST Gateway default RPC kind: unary only
- REST server-stream/client-stream/bidirectional-stream composition:
NOT_SELECTED - 관련 결정: VD-12, VD-13, VD-14, VD-23, VD-24, VD-25, VD-27, VD-29
- 상세 설계: Protobuf browser transport와 REST Gateway
- 상위 API 설계: API contract, Schema, Mapper와 Server State
- backend handoff: Backend API와 Server State contract
배경
현재 repository에는 authenticated .proto source, Buf module, buf.yaml,
buf.lock, buf.gen.yaml, immutable descriptor set, generated gateway,
generated OpenAPI, protobuf runtime dependency와 actual REST transcoding provider
evidence가 없다. 따라서 이 ADR은 production contract와 구현 순서를 정의하지만
capability 구현 완료 증거는 아니다.
VD-27의 gRPC-Web과 이 ADR의 REST Gateway는 같은 protobuf source를 사용할 수 있어도 같은 browser protocol이 아니다.
gRPC-Web
browser generated protobuf client
-> gRPC-Web binary/text frame
-> gRPC-Web proxy
REST transcoding
browser ordinary HTTP/JSON client
-> google.api.http route + ProtoJSON
-> REST/gRPC transcoding gateway
gRPC-Web의 frame, terminal trailer와 client runtime을 REST Gateway에 재사용하지 않는다. REST Gateway의 route, ProtoJSON, HTTP status/header와 OpenAPI contract를 gRPC-Web descriptor compatibility로 대신 증명하지도 않는다.
현재 installed REST reference는 다음 public 의미를 가진다.
- curated
{ success, data, error, meta }envelope - stable frontend error vocabulary와 redaction
- create의 승인된
200 | 201 - optional HTTP
ETag,304,If-Match,412 Idempotency-Key와 effect reconciliation- browser-facing auth/CORS/CSRF, body ceiling과 mapper
일반 transcoding gateway의 기본 ProtoJSON/status/error 출력은 이 contract와 동일하지 않다. 따라서 현재 reference operation은 curated BFF를 기본 선택으로 유지한다. 제품이 direct gateway를 선택할 때는 이 ADR의 별도 artifact와 public contract compatibility를 먼저 승인한다.
결정
1. Public protocol과 adapter를 혼용하지 않는다
지원 가능한 adapter profile은 다음처럼 분리한다.
CURATED_REST_BFF
PROTOBUF_REST_JSON_GATEWAY
GRPC_WEB_BINARY
GRPC_WEB_TEXT
CONNECT_PROTOCOL
한 semantic operation의 한 public endpoint는 하나의 exact profile에만 binding한다.
- HTTP media를 보고 runtime이 BFF와 gateway를 자동 선택하지 않는다.
- provider 장애를 이유로 command를 다른 protocol로 자동 replay하지 않는다.
- 같은 path에 BFF envelope와 direct ProtoJSON을 content negotiation으로 섞지 않는다.
- gRPC-Web proxy route를 HTTP/JSON REST route로 간주하지 않는다.
- 내부 gRPC service address, fully-qualified method와 metadata를 browser input으로 받지 않는다.
- shadow read는 secondary 결과를 사용자나 cache에 반영하지 않는다.
public adapter 변경은 동일 backend method를 사용하더라도 API contract migration 이다. N/N-1 route, payload, status, error와 cache evidence 없이 in-place 전환하지 않는다.
2. Topology와 현재 기본 선택
현재 기본:
Browser
-> CDN / reverse proxy / WAF
-> same-origin or approved-origin curated BFF
-> public REST contract enforcement
-> application service or internal gRPC service
제품 승인 뒤 가능한 direct topology:
Browser
-> CDN / reverse proxy / WAF
-> authentication / authorization edge
-> pinned REST transcoding gateway
-> exact generated route allowlist
-> ProtoJSON codec
-> internal gRPC application service
gateway deployable이 독립 process인지 application과 같은 process인지는 이 ADR의 핵심이 아니다. 다음 owner가 논리적으로 분리돼야 한다.
- edge: TLS, origin, request admission, encoded byte/rate ceiling
- gateway: HTTP route, path/query/body mapping, ProtoJSON, status/header projection
- application service: authorization 재검사, validation, transaction, idempotency, pagination, revision과 domain invariant
- persistence: durable dedupe, cursor/snapshot과 concurrency authority
3. Protobuf source of truth와 artifact
.proto source와 dependency는 authenticated immutable input이어야 한다.
ProtoContractArtifactV1
artifactId
contractVersion
sourceRepository
sourceCommit
sourceTreeDigest
bufModuleRef
bufModuleCommit
bufModuleDigest
bufLockDigest
bufImageId
bufImageDigest
fileDescriptorSetId
fileDescriptorSetDigest
includeImports
includeSourceInfo
httpRuleSource = PROTO_ANNOTATION | SERVICE_CONFIG
httpRuleArtifactId
httpRuleArtifactDigest
productionBaselineArtifactId
productionBaselineDigest
protoJsonProfileId
generatedGatewayArtifactId
generatedGatewayDigest
generatedOpenApiArtifactId
generatedOpenApiDigest
minimumBackendVersion
minimumGatewayVersion
retirementEpoch | null
owner
불변조건:
- moving branch, tag, label이나
latest를 production generation input으로 사용하지 않는다. - Buf Schema Registry를 사용하면 exact module commit/digest를 release에 고정한다.
buf.lock의 direct/transitive dependency commit과 digest를 check-in한다.google/api/annotations.proto,http.proto, well-known type dependency가 실제 descriptor input과 일치해야 한다.- Buf image와 native
google.protobuf.FileDescriptorSet을 구분해 저장한다. - descriptor digest는 exact artifact byte의 integrity/provenance 식별자다. digest가 다르다는 사실만으로 semantic breaking을 추측하지 않고 compatibility gate를 별도로 실행한다.
- production server reflection을 source/codegen 공급망으로 사용하지 않는다. reflection은 승인된 diagnostics profile에서만 사용할 수 있다.
- artifact download는 authenticated channel, integrity verification과 audit evidence를 가져야 한다.
4. Schema evolution과 breaking policy
공통 protobuf governance:
- field number 재사용 금지
- 제거한 field number와 name을 모두 reserve
- package/service/method full name 안정성
- 삭제 대신 deprecate와 retirement window
- proto3 optional/presence와 implicit default 의미 명시
- enum zero value와 unknown numeric value 처리
- 제거한 enum name/number reserve
- oneof absence, 새 case와 unknown case 처리
- map iteration order를 identity, signature와 pagination order로 사용하지 않음
- repeated/map/message depth와 decoded byte ceiling
- Timestamp/Duration valid range, nanos와 timezone projection
- int64/uint64를 JavaScript
number로 무조건 변환하지 않음 - bytes base64와 maximum decoded length
Any,Struct,Value는 type allowlist와 semantic validator 없이는 public contract에 넣지 않음
breaking baseline은 mutable main branch가 아니라 마지막으로 production에 promote된 immutable artifact다.
Buf category:
- generated gateway, generated server/client 또는 여러 언어 source compatibility를
보호하는 기본은
FILE이다. - source compatibility를 의도적으로 별도 관리해도 HTTP/JSON을 제공하는 module은
최소
WIRE_JSON을 통과해야 한다. - JSON을 사용하는 module에서
WIRE만 통과한 것을 REST compatibility 증거로 사용하지 않는다.
google.api.http와 다른 custom option의 의미는 generic Buf breaking rule만으로
닫히지 않는다. 따라서 다음을 별도 gate로 둔다.
- canonical route manifest semantic diff
- generated OpenAPI compatibility diff
- HTTP method/path/body/query/response body 변경 분류
- additional binding 추가/삭제/충돌 검사
- path escaping/unescaping profile diff
- auth, idempotency, pagination, conditional과 status profile diff
schema wire-safe 변경도 제품 의미, validation ceiling, pagination이나 auth scope를 바꾸면 semantic breaking일 수 있다. Buf success를 mapper/domain compatibility 승인으로 사용하지 않는다.
5. Code generation provenance와 reproducibility
generation toolchain:
CodegenToolchainV1
bufCliVersion
protocVersion
plugins[]:
pluginId
pluginVersion
pluginRevision
binaryDigest
optionsDigest
invocationStrategy
protobufRuntimeId
protobufRuntimeVersion
grpcRuntimeVersion
gatewayRuntimeVersion
languageRuntimeVersion
osImageDigest
- Buf CLI, protoc, every local/remote plugin과 runtime을 exact version으로 pin한다.
- BSR remote plugin은 upstream version뿐 아니라 repackaging revision도 고정한다.
- version을 생략해 latest plugin을 선택하지 않는다.
- local plugin은 package lock과 executable digest를 함께 기록한다.
- plugin option, managed-mode override, include/exclude path/type와 invocation strategy를 manifest에 포함한다.
- generated code보다 오래된 incompatible protobuf runtime을 조립하지 않는다.
buf.gen.yaml의 output 경계는 생성 전에 clean되며 generated directory에 hand-written file을 두지 않는다.
CI pipeline:
authenticated immutable source
-> dependency lock verification
-> buf format/lint
-> descriptor/image build
-> breaking check against production baseline
-> HttpRule route manifest generation/diff
-> pinned gateway/OpenAPI/runtime code generation
-> clean regenerate
-> worktree diff == 0
-> generated compile/typecheck/test
-> N/N-1 binary + ProtoJSON fixtures
-> provider conformance
-> artifact sign/attest/SBOM
-> release contract set
“Buf를 사용했다”는 이유만으로 deterministic generation이라 부르지 않는다. 동일 input/toolchain에서 clean regenerate diff가 0이고 artifact digest가 일치하는 증거를 남긴다. timestamp, absolute local path와 host-dependent output을 생성물에서 금지하거나 normalize한다.
generated gateway/server/message/OpenAPI는 application/domain model이 아니다. frontend가 generated OpenAPI client를 선택하더라도 transport adapter 밖으로 generated DTO를 노출하지 않고 VD-24 runtime schema와 mapper를 유지한다.
6. ProtoJSON profile
모든 gateway operation은 immutable ProtoJsonProfile에 binding한다.
ProtoJsonProfileV1
profileId
specificationRevision
useProtoNames
discardUnknownOnRequest
emitUnpopulated
useEnumNumbers
allowPartial
int64Projection
bytesProjection
nullPresencePolicy
duplicateFieldPolicy
timestampPolicy
durationPolicy
fieldMaskPolicy
anyTypeAllowlist[]
maxDecodedMessageBytes
maxDepth
maxNodes
maxStringBytes
maxRepeatedItems
maxMapEntries
reference 기본:
useProtoNames = false
discardUnknownOnRequest = false
emitUnpopulated = false
useEnumNumbers = false
allowPartial = false
int64Projection = DECIMAL_STRING
bytesProjection = STANDARD_BASE64
nullPresencePolicy = PROTOJSON_UNSET
duplicateFieldPolicy = REJECT_OR_PROVIDER_PINNED
wire 의미:
- JSON field name은 lowerCamelCase가 기본이며 proto field name 입력 수용 여부와 출력 option을 fixture로 고정한다.
- int64/uint64 출력은 decimal string이다. frontend mapper가 safe integer 범위를
증명하지 않는 한
number로 변환하지 않는다. - bytes는 base64를 decode하기 전 encoded/decoded ceiling을 모두 확인한다.
- enum output은 name이며 unknown numeric value를 application enum으로 추측하지 않는다.
- serializer는 presence가 없는 default field를 기본 생략한다.
null은 일반 field의 명시적 domain null이 아니라 protobuf unset으로 해석될 수 있다. nullable domain 의미는 별도 message/oneof/mapper가 소유한다.- unknown request field를 production에서 조용히 버리지 않는다.
- response additive rollout은 old frontend가 unknown field를 받는 N/N-1 fixture를 통과한 뒤 writer를 활성화한다.
- duplicate field를 사용하는 client 의미에 의존하지 않는다. selected runtime이 reject하지 않으면 deterministic last-value behavior와 WAF/parser differential test를 별도 증명한다.
- Timestamp/Duration/FieldMask/wrapper well-known type의 special JSON mapping을 ordinary object로 추측하지 않는다.
gateway decoder success는 semantic validation proof가 아니다. application service와 frontend boundary는 range, presence, enum/oneof, collection ceiling과 domain invariant를 각각 다시 검증한다.
7. HttpRule과 route manifest
기본 source of truth는 RPC의 google.api.http annotation이다.
외부 gRPC API service-config YAML은 proto를 직접 변경할 수 없거나 동일 service를 서로 다른 public API로 투영해야 하는 승인된 경우에만 사용한다.
- annotation과 service-config를 같은 RPC에 중복 정의하지 않는 것이 기본이다.
- service-config가 matching annotation을 override할 수 있으므로 artifact digest, selector, precedence와 generated route 결과를 release에 포함한다.
- config rule의 last-one-wins에 의존해 중복 selector를 숨기지 않는다.
generate_unbound_methods는 기본 false다.
RestGatewayRouteManifestV1
manifestId
protoContractArtifactId
httpRuleArtifactDigest
routes[]:
semanticOperationId
fullyQualifiedService
method
rpcKind
httpMethod
pathTemplate
additionalBindingIndex | null
pathFieldBindings
queryFieldBindings
requestBodyBinding | null
responseBodyBinding | null
protoJsonProfileId
operationProfileId
routeManifestDigest
HttpRule 불변조건:
- GET/DELETE operation은 request body를 갖지 않는다.
- path variable은 허용된 non-repeated primitive field에만 binding한다.
- body field는 top-level이며 path field와 중복되지 않는다.
body: "*"이면 path에 포함되지 않은 모든 field가 body로 가고 query parameter가 없음을 계약한다.- body를 생략하면 request body가 없고 나머지 field가 query로 가는 것을 계약한다.
- repeated primitive query의 repeated-key 의미와 maximum item을 고정한다.
- nested message query flattening은 approved field set과 depth ceiling을 가진다.
- additional binding 안에 additional binding을 중첩하지 않는다.
- 한 RPC의 additional binding이 다른 RPC route와 충돌하지 않는다.
- reserved path character와 multi-segment parameter는 selected gateway의
AllExceptReserved동등 unescaping profile과 actual fixture를 통과한다. - caller-provided custom HTTP verb와 arbitrary route를 허용하지 않는다.
PATCH가 FieldMask를 사용하면 gateway 자동-population 여부와 body: "*" 예외를
profile에 고정한다. partial update의 최종 authorization, writable field allowlist와
mask validation은 application service가 수행한다.
8. OpenAPI contract
generated OpenAPI는 documentation 부산물이 아니라 browser-facing compatibility artifact다.
GatewayOpenApiArtifactV1
generatorId
generatorVersion
generatorRevision
generatorOptionsDigest
routeManifestDigest
protoJsonProfileId
errorProfileId
statusProfileId
documentDigest
- production-stable pipeline은 selected grpc-gateway
protoc-gen-openapiv2version을 pin한다. - 공식 상태가 alpha인 OpenAPI v3 generator는 별도 승인과 toolchain conformance 전까지 production source of truth로 승격하지 않는다.
- generated document를 authenticated immutable artifact로 배포한다.
- generated path, parameter, request/response schema, status와 error가 actual gateway fixture와 일치해야 한다.
- runtime response rewriter처럼 OpenAPI에 표현되지 않는 변환은 direct gateway의 기본 public contract에서 금지한다.
- custom transformation이 필요하면 curated BFF를 선택하거나 별도 hand-written contract와 bidirectional conformance를 소유한다.
- OpenAPI client 생성 여부와 무관하게 frontend runtime boundary validation을 제거하지 않는다.
google.api.HttpBody는 raw/binary body를 전달할 수 있지만 일반 application
REST에 암묵적으로 사용하지 않는다. 파일 upload/download, Range, presigned URL,
background transfer와 CDN은 VD-12/VD-14의 transfer 계약을 유지한다.
9. Gateway provider와 operation definition
RestTranscodingProviderProfileV1
providerId
gatewayImplementation
gatewayVersion
fixedHttpsOrigin
basePathPrefix
routeManifestId
routeManifestDigest
protoContractArtifactId
protoJsonProfileId
openApiArtifactId
authProfileId
credentialsMode
corsProfileId
incomingHeaderProfileId
outgoingHeaderProfileId
pathUnescapingProfileId
errorProfileId
statusProfileId
requestAdmissionProfileId
redirect = ERROR
referrerPolicy
owner
ProtobufRestOperationV1
protocol = PROTOBUF_REST_JSON_V1
semanticOperationId
providerId
fullyQualifiedService
method
rpcKind = UNARY
requestMessageId
responseMessageId
protoContractArtifactId
descriptorDigest
routeManifestDigest
httpMethod
pathTemplate
requestBodyBinding | null
responseBodyBinding | null
requestSemanticSchemaId
responseSemanticSchemaId
mapperId
successStatusMediaProfileId
errorProfileId
authProfileId
csrfProfileId
replayPolicy
idempotencyProfileId | null
deadlineProfileId
retryProfileId
paginationProfileId | null
conditionalProfileId | null
serverStateProfileId | null
maxUrlBytes
maxRequestHeaderBytes
maxRequestBodyBytes
maxDecodedRequestBytes
maxResponseHeaderBytes
maxEncodedResponseBytes
maxDecodedResponseBytes
maxResponseItems
owner
composition이 거절:
- unknown service/method/message/descriptor
- descriptor, route manifest, ProtoJSON과 OpenAPI digest mismatch
- operation과 provider profile mismatch
- client-streaming 또는 bidirectional RPC
- 별도 streaming ADR 없는 server-streaming RPC
- caller-provided URL, service, method, metadata와 header
- body/query/path/status가 generated route와 다름
- ceiling, deadline, auth, error 또는 owner가 없음
- non-replayable command에 retry
- keyed command에 durable idempotency evidence가 없음
10. Unary-only default와 streaming 경계
direct REST Gateway reference는 unary RPC만 허용한다.
- unary request 한 개와 unary response 한 개
- exact terminal HTTP status/media/body
- bounded response decode
grpc-gateway의 server stream은 newline-separated JSON chunk envelope와 terminal error body를 사용할 수 있다. 이것은 ordinary REST JSON response도, VD-27의 gRPC-Web framing도 아니다.
따라서 다음은 NOT_SELECTED다.
- REST server stream
- REST client stream
- REST bidirectional stream
- streaming
HttpBodyfile delivery를 ordinary API adapter로 사용
제품이 server stream을 요구하면 chunk media type, message delimiter, partial-delivery meaning, terminal error, backpressure, sequence/gap/resume, idle/total deadline과 browser buffering을 별도 ADR과 actual provider evidence로 설계한다. SSE, gRPC-Web, WebSocket 또는 bounded polling과도 요구를 다시 비교한다.
11. Direct gateway와 curated BFF 선택
| 요구 | 기본 선택 |
|---|---|
google.api.http와 canonical ProtoJSON이 그대로 public contract |
direct gateway 가능 |
| simple unary resource operation, 별도 envelope/aggregation 없음 | direct gateway 가능 |
현재 { success, data, error, meta } envelope 유지 |
curated BFF |
| 제품별 DTO projection, aggregation 또는 여러 backend orchestration | curated BFF |
| cookie session, CSRF와 same-origin session lifecycle | curated BFF |
| stable safe error vocabulary와 PII redaction | curated BFF 권장 |
custom 201/204, HTTP 304/412와 conditional cache transaction |
curated BFF 또는 별도 custom gateway |
| idempotency receipt/status/reconcile UX | curated BFF 권장 |
| file transfer, Range, presigned URL, background download와 Image CDN | transfer/BFF 경계 |
| server streaming | 별도 protocol ADR |
direct gateway는 다음을 모두 증명할 때만 선택한다.
- public DTO가 canonical ProtoJSON과 일치
- no custom success envelope
- exact google.api.http route가 product REST route와 일치
- safe error/status mapping이 frontend failure vocabulary와 호환
- edge/gateway/upstream auth owner가 명확
- operation이 unary이고 bounded
- custom header/status/cache 의미가 제한적이고 artifact에 표현 가능
- actual browser/gateway/backend conformance 통과
BFF 뒤에서 internal gRPC를 호출하는 것은 direct gateway가 아니다. public contract owner는 계속 BFF다.
12. Request admission과 semantic validation
request 처리:
TLS/WAF encoded admission
-> fixed route/method
-> origin/auth/CSRF/rate admission
-> header/path/query/body byte ceiling
-> HttpRule binding
-> strict ProtoJSON decode
-> generated request message
-> application semantic validation
-> authorization
-> application service
- edge는 encoded request/body/header와 request rate ceiling을 소유한다.
- gateway는 URL/path/query/body mapping과 decoded protobuf message ceiling을 소유한다.
- application service는 generated message를 신뢰하지 않고 domain validation과 authorization을 다시 수행한다.
- path와 body에 같은 field가 중복되거나 충돌하면 fail-closed한다.
- unknown query/body field, malformed percent encoding과 parser differential을 거절한다.
- decompression ratio, nested depth, repeated/map count와 string/bytes length에 별도 ceiling을 둔다.
- error body도 bounded writer와 redaction을 통과한다.
13. Success status, body와 media
operation은 exact success matrix를 가진다.
GatewaySuccessProfileV1
allowedHttpStatuses[]
responseMediaTypes[]
bodyPolicy = REQUIRED | EMPTY | PROTOJSON_MESSAGE
responseHeaderAllowlist[]
responseBodyMessageId | null
- default transcoded unary success를 임의로
201또는204로 추측하지 않는다. google.protobuf.Empty의 default200 {}와204는 같은 contract가 아니다.201 Created,Location,204 No Content가 필요하면 generated OpenAPI, gateway hook, actual fixture와 rollback을 함께 승인한다.- upstream metadata로 status를 바꾸는 기능은 registered response type/method와 allowlisted value만 허용한다.
- caller가
x-http-code같은 internal control metadata를 제출하지 못한다. - status hook 뒤 body가 이미 write된 상태에서 status를 변경하지 않는다.
- response body rewrite가 OpenAPI에 반영되지 않으면 direct public gateway에서 사용하지 않는다.
- HTML proxy error, redirect login page와 unexpected media를 ProtoJSON으로 해석하지 않는다.
현재 curated REST envelope를 direct ProtoJSON으로 조용히 바꾸지 않는다. direct gateway adoption에는 새 operation/version 또는 증명된 compatibility facade가 필요하다.
14. Error contract와 redaction
gateway error profile:
GatewayErrorProfileV1
grpcToHttpStatusMapId
publicEnvelope = GOOGLE_RPC_STATUS | CURATED_APP_FAILURE
safeReasonDomainAllowlist[]
safeDetailTypeAllowlist[]
exposeGrpcMessage = false
exposeUnknownDetails = false
maximumErrorBytes
maximumDetails
- gRPC canonical status와 HTTP status mapping을 exact gateway revision에 pin한다.
- routing
404/405/400과 upstream application error를 구분한다. Status.message는 machine branch key가 아니다.- arbitrary
Status.message, stack, SQL/vendor copy, internal address와 rawAny.details를 frontend에 노출하지 않는다. - frontend branch는 registered stable reason/code/category만 사용한다.
ErrorInfo를 사용하면(reason, domain)과 approved metadata key를 versioned vocabulary로 관리한다.- authorization detail은 resource 존재 여부나 policy reason을 누출하지 않는다.
- malformed/oversize upstream error는 safe generic provider failure로 닫는다.
- unary custom error handler와 routing error handler가 actual OpenAPI/fixture와 일치해야 한다.
- stream error handler는 unary handler와 다르므로 streaming이 선택되기 전 production guarantee로 표시하지 않는다.
partial success/error는 기본 거절한다. bulk partial semantics가 필요하면 long-running operation이나 별도 result contract를 선택한다.
15. Authentication, authorization, CORS와 CSRF
gateway는 authorization authority를 자동 제공하지 않는다.
- edge 또는 gateway가 credential을 검증해도 application service가 operation과 resource authorization을 다시 수행한다.
- bearer profile은 issuer, audience, signature algorithm, time claims, revocation과 tenant binding을 검증한다.
- cookie profile은
Secure,HttpOnly, explicitSameSite, host/path scope와 unsafe method CSRF를 함께 승인한다. - credentialed CORS에 wildcard origin을 사용하지 않는다.
- exact origin/method/header/expose 목록과 bounded preflight cache를 fixture로 검증한다.
grpc-gateway의 incoming Authorization은 upstream gRPC metadata로 전달될 수
있다. 이 동작을 다음처럼 다룬다.
- public bearer를 upstream service가 검증하는 profile인지 명시
- edge가 identity를 변환하면 original credential 전달/제거 owner를 별도 설계
- 외부 caller가 내부 principal, tenant, role, trace와 policy header를 spoof하지 못하도록 edge에서 제거
- incoming/outgoing header matcher는 closed allowlist
- hop-by-hop, cookie, raw credential, internal debug와 arbitrary
grpc-*metadata를 forwarding하지 않음 - auth attach 뒤 final origin/path/method/body digest와 registry binding 재검증
current cookie/same-origin이나 curated auth/error/meta 요구가 크면 BFF를 선택한다.
16. Deadline과 cancellation
browser total deadline
-> edge admission
-> gateway request context
-> upstream gRPC deadline
-> application/downstream deadline propagation
- gRPC는 deadline이 기본으로 자동 설정된다고 가정하지 않는다.
- frontend total deadline 안에서 edge/gateway/upstream phase가 더 짧은 ceiling을 가진다.
- retry/backoff가 total deadline을 늘리지 않는다.
- gateway는 remaining budget을 upstream deadline으로 전달하고 queue/connection time을 제외하지 않는다.
- browser AbortSignal/navigation/logout와 connection close를 구분된 safe cancellation reason으로 정규화한다.
- HTTP disconnect가 upstream gRPC cancel과 application work stop으로 이어지는지 actual provider에서 검증한다.
- server handler와 spawned/downstream work는 cancellation을 주기적으로 확인하고 불필요한 계산/I/O를 중단한다.
- deadline/cancel 뒤 timer, request body, response writer와 upstream call을 정리한다.
cancellation은 이미 commit된 effect를 rollback하지 않는다. client와 server의 success 판단이 다를 수 있으므로 command는 cancellation 결과만으로 effect 없음을 선언하지 않는다.
17. Retry와 idempotency
semantic replay policy:
SAFE
IDEMPOTENT
KEYED_COMMAND
NON_REPLAYABLE
- gateway의 모든 upstream gRPC call이 HTTP POST라는 이유로 retry-safe라 간주하지 않는다.
- frontend, CDN/proxy, gateway, gRPC client와 service retry 중 한 owner만 physical retry를 수행한다.
- automatic retry 기본 대상은 bounded unary safe read다.
- transactional sequence,
ABORTED, validation, auth와 non-replayable command를 transport가 자동 retry하지 않는다. - response header/body가 시작된 뒤 retry하지 않는다.
- attempt count, backoff, sleep, elapsed와 total deadline ceiling을 둔다.
idempotency transport는 operation마다 하나를 선택한다.
HTTP_IDEMPOTENCY_KEY
PROTO_REQUEST_ID
NONE
- 현재 curated REST의
Idempotency-Key를 direct gateway가 사용하면 exact incoming metadata mapping과 upstream binding을 정의한다. - protobuf
request_idfield를 사용하면 HttpRule body/query 위치, UUID format, payload fingerprint와 replay window를 정의한다. - 두 identity를 동시에 받아 어느 값을 authority로 쓸지 추측하지 않는다.
- gateway process-local map/lock을 idempotency store로 사용하지 않는다.
- principal/tenant + semantic operation/version + key + canonical request fingerprint를 durable store에 binding한다.
- same key/same fingerprint replay는 authoritative prior result/receipt를 반환한다.
- same key/different fingerprint는 stable conflict다.
- commit 뒤 response loss는 새 effect를 만들지 않는다.
EFFECT_UNKNOWN은 새 key나 다른 protocol retry가 아니라 status/reconcile endpoint로 확인한다.- retention은 frontend recovery window보다 길고 quota/abuse policy를 가진다.
18. Pagination
HttpRule/gateway는 protobuf field를 HTTP query/body로 옮길 뿐 pagination 의미를 구현하지 않는다.
paginated list는 첫 public version부터 다음을 포함한다.
ListRequest
pageSize
pageToken
ListResponse
items[]
nextPageToken
application/backend 책임:
- finite default와 maximum page size
- negative size rejection과 oversized coercion/approved policy
- opaque URL-safe page token
- principal/tenant/filter/sort/contract/snapshot binding
- token integrity, expiry와 key rotation
- stable total ordering과 immutable unique tie-breaker
- insertion/deletion 중 snapshot or documented consistency
- duplicate/gap/loop 방지
- next token empty만 end-of-collection 의미
- page token이 authorization을 대체하지 않음
unpaginated RPC에 field만 추가하고 default response를 일부로 줄이는 변경은 behavioral breaking이다. 현재 reference array response를 page object로 바꾸려면 새 operation/schema와 N/N-1 migration을 사용한다.
frontend는 arbitrary next URL을 따라가지 않고 opaque token만 registered query input에 투영한다. raw token을 log, analytics와 persistent cache에 넣지 않는다.
19. ETag, revision과 HTTP conditional
다음 두 profile을 구분한다.
PROTO_RESOURCE_ETAG
HTTP_REPRESENTATION_CONDITIONAL
PROTO_RESOURCE_ETAG:
- resource/request message의
string etagfield - server-owned output
- strong/weak meaning 명시
- mismatch는 selected gRPC status, 보통
ABORTED - ProtoJSON body/query field로 왕복
HTTP_REPRESENTATION_CONDITIONAL:
- HTTP response
ETag - request
If-None-Match또는If-Match - unchanged read의
304empty body - failed write precondition의 approved
412 | 409 - exact principal/tenant-visible representation과 encoding variant binding
Cache-Control,Vary, CORS expose/preflight와 frontend cache transaction
proto etag field가 존재한다고 gateway가 HTTP ETag, 304 또는 If-Match를
자동 제공한다고 간주하지 않는다. body ETag를 HTTP validator로 투영하려면
allowlisted response header hook, request header mapping, status conversion,
generated/curated OpenAPI와 actual cache fixture가 필요하다.
현재 REST reference의 TanStack/application conditional transaction은
HTTP_REPRESENTATION_CONDITIONAL이다. direct gateway가 이 의미를 정확히
구현하지 않으면 curated BFF를 유지한다.
frontend는 validator와 mapped cached value의 scope/query identity/representation version/cache revision이 모두 일치할 때만 304를 success로 처리한다. raw validator를 log, diagnostics와 metric label에 넣지 않는다.
20. Cache와 server state
gateway adoption이 frontend query identity를 바꾸지 않게 semantic operation input을 canonical source로 유지한다.
- generated path/query serializer와 query-key codec가 같은 validated application input에서 파생
- ProtoJSON DTO와 generated message를 TanStack Query cache에 저장하지 않음
- schema/semantic validation과 mapper가 끝난 immutable application projection만 저장
- auth/session/tenant generation이 cache scope를 소유
- late response와 retry result는 generation fence 뒤 cache에 들어가지 않음
- protocol migration 중 BFF와 gateway 결과를 같은 key에 섞지 않음
- pagination token과 ETag는 domain resource data와 별도 bounded sidecar owner
- mutation commit 뒤 exact invalidation/reconcile owner 필요
21. Observability, privacy와 abuse control
허용 dimension:
semantic operation ID
gateway/provider profile ID
proto contract artifact version
route manifest version
ProtoJSON profile ID
status group / safe error code
attempt bucket
duration and size bucket
admission/cancellation stage
금지:
- raw Authorization/cookie/CSRF/idempotency key
- raw path/query/body와 protobuf message
- cursor/page token/ETag/revision
Status.message, unknown details와 validation value- principal, tenant, resource ID와 IP의 unbounded label
- descriptor/proto source URL credential
gateway는 operation별 request rate, concurrent upstream call, body/response byte, CPU-heavy JSON decode, error volume와 upstream saturation ceiling을 가진다.
최소 metric:
- route admission/rejection
- ProtoJSON malformed/unknown/oversize
- descriptor/route/OpenAPI artifact mismatch
- gRPC→HTTP status/error category
- deadline/cancel propagation stage
- retry attempts/amplification
- idempotency replay/conflict/unknown effect
- page token invalid/expired
- conditional hit/miss/precondition failure
- provider/browser conformance version
22. Provider conformance와 promotion evidence
Deterministic
- Buf format/lint/breaking
- exact production baseline
- source/dependency/image/descriptor/toolchain/generated digest
- clean regenerate diff 0
- gencode/runtime compatibility
- generated import boundary와 SBOM
- HttpRule custom-option semantic diff
Contract
- descriptor N/N-1 binary fixture
- ProtoJSON scalar/presence/null/unknown/enum/int64/bytes/time matrix
- method/path/body/query/additional binding
- path percent-encoding and
AllExceptReservedbehavior - generated OpenAPI vs actual request/response
- exact success/media/status/body
- safe unary/routing error mapping
- auth/header allowlist
- idempotency/pagination/conditional fixtures
Integration/browser
- actual gateway version + actual gRPC backend
- Chromium/Firefox/WebKit fetch
- CORS/preflight, bearer 또는 cookie+CSRF
- reverse proxy/CDN/WAF body and header ceiling
- AbortSignal/disconnect/deadline propagation
- navigation/logout/account switch
- HTTP/1.1, HTTP/2와 intermediary error behavior
Fault/security
- malformed/oversize/truncated JSON
- unknown/duplicate field와 parser differential
- route collision, encoded slash와 path traversal
- spoofed identity/internal control header
- upstream unavailable/deadline/status mismatch
- error detail/stack/PII redaction
- response started 뒤 failure/retry
- commit 뒤 response loss와 idempotency reconciliation
- invalid/expired/wrong-scope page token
- body ETag와 HTTP conditional 혼동 방지
Performance/operations
- bounded JSON transcoding CPU/memory
- maximum request/response와 concurrent load
- slow client/upstream and queue saturation
- canary, kill switch와 traffic admission
- coherent proto/gateway/OpenAPI/frontend/backend rollback
- generated/proxy/dependency removal drill
fake gateway, generated compile과 OpenAPI 생성 success만으로 actual provider conformance를 완료 처리하지 않는다.
23. Rollout
- backend/product owner가 direct gateway가 필요한 operation과 BFF 대비 이점을 승인한다.
- authenticated proto source, exact production baseline과 artifact owner를 확정한다.
- Buf/codegen toolchain, breaking category와 HttpRule source를 pin한다.
- descriptor, route manifest, ProtoJSON profile와 OpenAPI artifact를 생성한다.
- provider/gateway version, auth/error/status/deadline ceiling을 승인한다.
- unary safe read 한 개로 reference conformance harness를 만든다.
- frontend adapter를
AVAILABLE_NOT_COMPOSED로 판정한다. - product composition 뒤
TrafficAdmission=DISABLED로 배포한다. - internal/staging shadow read는 cache/UI에 반영하지 않고 BFF result와 semantic diff를 관찰한다.
- actual browser canary를 낮은 비율로 시작한다.
- error/latency/schema/cache SLO와 artifact digest가 정상일 때 단계적으로 확대한다.
- keyed command는 durable dedupe/reconcile fault evidence 뒤 별도 canary한다.
- server stream은 계속
NOT_SELECTED다.
한 release에서 proto writer, gateway route, error envelope와 frontend consumer를 동시에 breaking 전환하지 않는다. additive reader-first/writer-later와 N/N-1 window를 사용한다.
24. Rollback과 removal
rollback:
- 신규 gateway operation traffic admission 중지
- in-flight safe read cancel
- command는 cancel 뒤 authoritative receipt/status로 effect reconcile
- gateway-scoped query/validator/pagination sidecar clear 또는 invalidate
- last-known-good proto/descriptor/route/OpenAPI/gateway/backend/frontend artifact를 coherent하게 복구
- approved BFF read fallback은 새 logical request와 독립 total deadline으로만 실행
- command를 BFF나 gRPC-Web로 자동 replay하지 않음
removal:
- operation traffic과 route retirement window 시작
- N/N-1 frontend/backend/gateway 소비자를 확인
- route manifest와 traffic admission에서 제거
- frontend operation/schema/mapper/cache profile 제거
- generated gateway/OpenAPI/message output 제거
- gateway route, proxy/CORS/auth/config 제거
- plugin/runtime/package와 SBOM entry 제거
- proto RPC는 다른 소비자가 없고 deprecation window가 끝난 뒤 제거
- message field/enum number와 name은 제거 시 reserve
- descriptor/baseline artifact는 audit/retention 정책에 따라 보존
- build, bundle, dependency, provider probe와 removal drill 통과
25. Backend/provider handoff checklist
- proto source/module owner와 immutable production baseline이 있다.
buf.lock, Buf image, native descriptor와 digest가 release에 binding됐다.- Buf/protoc/plugin version/revision/options/runtime이 pin됐다.
FILE또는 승인된 최소WIRE_JSONbreaking gate가 있다.- HttpRule custom-option와 generated OpenAPI semantic diff gate가 있다.
- ProtoJSON profile과 N/N-1 fixture가 있다.
- direct gateway와 curated BFF 중 operation별 public owner가 하나다.
- exact unary service/method/route/status/media/error가 allowlist됐다.
- bearer 또는 cookie+CSRF/CORS provider evidence가 있다.
- incoming/outgoing header와 internal identity spoofing 방지가 검증됐다.
- edge/gateway/upstream byte/rate/depth/count ceiling이 있다.
- total deadline, browser abort와 downstream cancellation evidence가 있다.
- retry owner가 하나이고 command idempotency/reconcile가 durable하다.
- pagination을 선택했다면 token/snapshot/authorization contract가 있다.
- conditional을 선택했다면 body ETag와 HTTP validator profile이 분리됐다.
- generated OpenAPI와 actual gateway wire fixture가 일치한다.
- actual browser/gateway/backend canary, kill switch와 rollback drill이 통과했다.
- generated code, proxy route와 dependency removal drill이 통과했다.
규범 기준
- Protocol Buffers ProtoJSON format
- Protocol Buffers proto3 language guide
- Protocol Buffers cross-version runtime guarantee
- Buf breaking-change detection
- Buf breaking rules and categories
- Buf code generation
- Buf
buf.gen.yamlv2 - Buf dependency management
- Buf FileDescriptorSet
- Google API HTTP and gRPC transcoding
- Google API errors
- Google API request identification
- Google API pagination
- Google API resource freshness validation
- Google API
HttpRule - gRPC-Gateway introduction
- gRPC-Gateway FAQ
- gRPC-Gateway customization
- gRPC-Gateway PATCH
- gRPC-Gateway HttpBody
- gRPC-Gateway OpenAPI v3 status
- gRPC deadlines
- gRPC cancellation
- gRPC status codes
완료 기준
- Protobuf source/dependency/descriptor/toolchain/generated artifact의 provenance와 digest가 production baseline에 binding된다.
- Buf binary/source compatibility와 HttpRule/OpenAPI semantic compatibility를 별도 gate로 검증한다.
- ProtoJSON field/int64/bytes/enum/null/presence/unknown 의미가 exact profile로 닫힌다.
- direct REST Gateway와 gRPC-Web/curated BFF가 서로 다른 protocol artifact와 state machine을 가진다.
- 현재 installed REST reference는 별도 product 승인 전 curated BFF를 유지한다.
- direct gateway operation은 unary, fixed route/service/method와 bounded request/response에만 compose된다.
- status/body/error/auth/header/deadline/cancel/retry가 actual provider fixture와 일치한다.
- gateway가 idempotency, pagination, revision과 authorization authority를 대신한다고 표시하지 않는다.
- body ETag와 HTTP conditional semantics를 혼동하지 않는다.
- generated OpenAPI와 actual wire contract drift가 promotion을 차단한다.
- actual browser/gateway/backend conformance, canary, kill switch, coherent rollback과 removal drill이 통과한다.
- schema/codegen implementation 전에는
DESIGNED_NOT_IMPLEMENTED, product 선택 전에는 REST Gateway를NOT_SELECTED로 유지한다.