The product was materialized from the template at `4dc033c` and has stayed on it through 43 template commits, so it was missing all three rounds of adapter remediation — including files it never had, such as the shared `abortable-operation` primitive and the `exact-snapshot` decoder that later fixes are written against. Taking only the newest round was not possible for that reason: the delta is coherent only as a whole. The product had not touched `src/adapters` at all since materialization, so the 140-file delta applied with a three-way merge and no conflicts. `package.json` was the single overlap and merged cleanly: the product owns `name`, the template contributed `check:adapter-inventory`, `check:remediation-ledger` and the image-resolve-signal type fixture. All 24 product-owned files — README, index.html, CI workflow, i18n catalog, home page, generated schemas, evidence scripts, component and visual snapshots — are byte-identical to `main`. `template.lock.json` now pins the synced revision and tree. Verified in this repository, not inherited from the template: six type projects, lint, nine gates (adapter inventory, remediation ledger, registries, diagnostics, realtime boundaries, architecture, browser file/storage boundaries, optional recipes, documentation), the production build, and 2,054 of 2,073 tests. The 19 failures are all in `tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template records; four suites that failed once under parallel load pass in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
807 lines
36 KiB
Markdown
807 lines
36 KiB
Markdown
# Protobuf browser transport와 REST Gateway
|
|
|
|
- 상태: production design accepted, product/provider selection pending
|
|
- 기준일: 2026-07-28
|
|
- 범위: gRPC-Web, Connect-Web, Connect protocol, Protobuf contract/codegen,
|
|
REST/JSON Gateway
|
|
- 현재 source 상태: provider-neutral Browser RPC V3 계약·application port·공통
|
|
unary/server-stream lifecycle runtime은 `AVAILABLE_NOT_COMPOSED`;
|
|
vendor runtime/dependency/proto/descriptor/proxy는 없음
|
|
- 관련 결정:
|
|
- [VD-23 API transport selection과 REST execution](./decisions/VD-23-api-transport-selection-and-rest-execution.md)
|
|
- [VD-24 Runtime schema와 boundary mapper](./decisions/VD-24-runtime-schema-and-boundary-mapper.md)
|
|
- [VD-25 Server state cache lifecycle](./decisions/VD-25-server-state-cache-lifecycle.md)
|
|
- [VD-27 gRPC-Web unary와 server stream](./decisions/VD-27-grpc-web-unary-and-server-stream.md)
|
|
- [VD-29 Connect-Web와 browser Protobuf runtime](./decisions/VD-29-connect-web-and-browser-protobuf-runtime.md)
|
|
- [VD-30 Protobuf contract와 REST Gateway](./decisions/VD-30-protobuf-contract-and-rest-gateway.md)
|
|
- backend handoff:
|
|
[Backend API와 Server State contract](./backend-api-and-server-state-contract.md)
|
|
- 운영 절차:
|
|
[API contract와 server-state recovery](../operations/api-contract-and-server-state-recovery.md)
|
|
|
|
|
|
## Installed binding snapshot과 stream cleanup bound (R-01, R-04, R-05, R-06)
|
|
|
|
- `installBrowserRpcContractBindings()`가 registry를 **parse → validate →
|
|
install** 순서로 처리한다. own data descriptor만 읽어 exact key set으로
|
|
null-prototype frozen snapshot을 만들고, 그 snapshot을 검증한 뒤 설치한다.
|
|
getter/accessor, extra key, symbol key, malformed descriptor, revoked proxy는
|
|
composition-time `TypeError`이며 getter는 호출조차 되지 않는다. runtime과
|
|
transport call은 이후 snapshot만 읽으므로 validation 이후 registry mutation이
|
|
replay policy·deadline·byte ceiling·transport selection을 바꿀 수 없다.
|
|
- server stream 종료는 transport iterator에 lifecycle authority를 위임하지
|
|
않는다. commit/admission generation은 즉시 fence하고 listener는 바로 해제하며,
|
|
`iterator.return()`은 cleanup **요청**으로서 bound 안에서만 기다린다. 끝나지
|
|
않은 cleanup은 관찰만 유지되고(unhandled rejection 없음) application generator는
|
|
bound 안에 종료된다. cleanup rejection은 이미 선택된 application failure를
|
|
덮지 않는다.
|
|
- WebSocket text frame은 allocation 전에 admission한다. UTF-16 code unit 길이가
|
|
이미 cap을 넘으면 encoder를 만들지 않고 거절하고, 나머지는 early exit하는
|
|
code-point 누적으로 센다. valid surrogate pair는 4 bytes, lone surrogate는
|
|
`TextEncoder`와 동일하게 replacement 3 bytes다.
|
|
- clock/fence collaborator 예외는 Result 경계를 벗어나지 않는다. clock 실패는
|
|
`SERVER_FAILURE/RPC_RUNTIME_DEPENDENCY_FAILED`, capture 실패는
|
|
`SCOPE_GENERATION_CHANGED/RPC_SCOPE_GENERATION_UNAVAILABLE`, `isCurrent` 실패는
|
|
fail-closed로 canonicalize하며 listener/timer는 단일 exit path에서 정확히 한 번
|
|
해제한다.
|
|
|
|
Browser RPC는 여전히 `AVAILABLE_NOT_COMPOSED`다. 선택된 Connect/gRPC-Web
|
|
transport는 enqueue-time `maxBufferedBytes`, raw/decompressed ceiling,
|
|
cancel/closed receipt, terminal framing, target browser와 load behavior를
|
|
별도로 증명해야 조립할 수 있다 (R-07).
|
|
|
|
## 1. 먼저 축을 분리한다
|
|
|
|
네 이름은 같은 종류의 대안이 아니다.
|
|
|
|
| 축 | 선택지 | 소유하는 것 |
|
|
| --- | --- | --- |
|
|
| contract/serialization | Protobuf binary, ProtoJSON | message/service schema, field number, presence, codegen |
|
|
| browser RPC transport | Connect protocol, gRPC-Web | HTTP framing, media, status/error, timeout, stream terminal |
|
|
| browser client runtime | Connect-Web, official grpc-web 또는 승인 runtime | Fetch/XHR, generated client, interceptors, cancellation |
|
|
| HTTP exposure | curated REST BFF, generated gRPC-Gateway, Envoy JSON transcoder | resource URL, method, JSON/status/cache/CORS contract |
|
|
|
|
예를 들어 Connect-Web은 같은 generated service descriptor로 Connect protocol과
|
|
gRPC-Web transport를 모두 만들 수 있다. 반대로 REST Gateway는 Protobuf service를
|
|
ProtoJSON HTTP API로 노출할 수 있지만, 그것이 자동으로 좋은 browser REST
|
|
contract가 된다는 뜻은 아니다.
|
|
|
|
한 semantic operation은 frontend registry에서 정확히 하나의 active wire
|
|
profile에 binding한다. runtime이 media type을 보고 Connect/gRPC-Web/REST를
|
|
추측하거나 장애 시 다른 protocol로 같은 command를 자동 replay하지 않는다.
|
|
|
|
## 2. 현재 상태
|
|
|
|
| capability | current status | 근거 | promotion 필요 |
|
|
| --- | --- | --- | --- |
|
|
| Browser RPC V3 공통 계약/runtime | `AVAILABLE_NOT_COMPOSED` | exact operation/profile/schema/mapper/encoder/transport join, unary retry·total deadline·abort, bounded server-stream·idle deadline·terminal, generation fence와 unavailable adapter test | selected generated client를 감싸는 protocol transport, raw-byte cap과 actual provider/browser conformance |
|
|
| Protobuf schema/codegen | `DESIGNED_NOT_IMPLEMENTED` | `.proto`, `buf.yaml`, descriptor, runtime dependency 없음 | authenticated schema source와 owner |
|
|
| Connect-Web client runtime | `DESIGNED_NOT_IMPLEMENTED` | `@connectrpc/*`, `@bufbuild/protobuf` 없음 | selected service, provider와 bundle budget |
|
|
| Connect protocol unary | `DESIGNED_NOT_IMPLEMENTED` | endpoint/profile/fixture 없음 | exact JSON 또는 binary profile |
|
|
| Connect protocol server stream | `DESIGNED_NOT_IMPLEMENTED` | stream runtime/proxy evidence 없음 | bounded stream protocol과 browser evidence |
|
|
| gRPC-Web unary/server stream | `DESIGNED_NOT_IMPLEMENTED` | VD-27만 존재 | runtime/proxy/descriptor와 conformance |
|
|
| REST Gateway reference contract/harness | `DESIGNED_NOT_IMPLEMENTED` | native REST reference만 있고 transcoder/fixture 없음 | selected kind의 deterministic/provider conformance |
|
|
| product REST Gateway composition | `NOT_SELECTED` | route/provider/owner 없음 | curated BFF 또는 selected transcoder의 제품 승인 |
|
|
| browser client/bidi stream | `PLATFORM_LIMITED` | request streaming을 target browser 공통 계약으로 보장 못함 | 다른 transport/application protocol |
|
|
| product traffic | `NOT_SELECTED` | operation/provider/owner 없음 | product ADR와 traffic admission |
|
|
|
|
이 문서가 추가돼도 dependency나 generated source를 기본 bundle에 넣지 않는다.
|
|
공통 runtime source가 생긴 뒤에도 같은 원칙을 유지한다. 공통 runtime은
|
|
Connect/gRPC-Web wire를 직접 decode하지 않고 selected transport가 반환한
|
|
bounded message/terminal/failure만 처리하므로, 실제 Connect-Web 또는 gRPC-Web
|
|
adapter가 구현됐다는 증거가 아니다.
|
|
|
|
## 3. 기본 선택
|
|
|
|
### 3.1 권장 순서
|
|
|
|
1. 기존 REST가 제품 의미와 운영 요구를 충족하면 REST를 유지한다.
|
|
2. backend가 Protobuf-first이고 browser RPC가 필요하면 Connect-Web +
|
|
Connect protocol을 우선 평가한다.
|
|
3. backend가 gRPC-Web만 노출하거나 기존 Envoy/gRPC-Web conformance 자산을
|
|
재사용해야 하면 Connect-Web의 gRPC-Web transport 또는 official grpc-web
|
|
runtime 중 하나를 고정한다.
|
|
4. public HTTP API, CDN/conditional cache, 링크 가능한 resource URL,
|
|
broad HTTP tooling이 핵심이면 curated REST BFF를 우선한다.
|
|
5. `google.api.http` annotation만으로 제품 REST 의미를 온전히 표현할 수 있을
|
|
때만 generated gRPC-Gateway/Envoy transcoding을 선택한다.
|
|
|
|
### 3.2 선택 matrix
|
|
|
|
| 요구 | 기본 후보 | 주의 |
|
|
| --- | --- | --- |
|
|
| 내부 web UI + Protobuf-first backend + unary | Connect-Web/Connect | JSON/binary를 operation profile로 고정 |
|
|
| 내부 web UI + 기존 gRPC-Web gateway | selected gRPC-Web runtime | runtime별 streaming capability가 다름 |
|
|
| bounded server→browser stream | Connect server stream 또는 gRPC-Web server stream | idle/total/queue/sequence/resume 필수 |
|
|
| public/resource-oriented HTTP API | curated REST BFF | Protobuf service shape를 그대로 노출하지 않음 |
|
|
| 단순 proto HTTP annotation과 broad JSON client | generated REST Gateway | ProtoJSON/status/error semantic fixture 필요 |
|
|
| HTTP cache/ETag/Range/file download | REST/BFF | RPC transcoder로 억지로 만들지 않음 |
|
|
| browser client/bidi | WebSocket/WebTransport/별도 session API | Connect protocol 자체 지원과 browser 지원을 혼동 금지 |
|
|
| 기존 REST backend뿐임 | REST 유지 | Protobuf/gateway를 미래 대비로 추가하지 않음 |
|
|
|
|
Connect가 항상 gRPC-Web보다 우월하거나 REST Gateway가 수동 REST보다 항상
|
|
저렴하다고 가정하지 않는다. actual provider, proxy, browser, bundle,
|
|
observability와 조직 운영 비용을 evidence로 비교한다.
|
|
|
|
## 4. 공통 application 경계
|
|
|
|
```text
|
|
presentation
|
|
-> feature application input
|
|
-> semantic gateway port
|
|
-> installed operation registry
|
|
-> REST adapter
|
|
-> Connect adapter
|
|
-> gRPC-Web adapter
|
|
-> transport decode
|
|
-> semantic runtime schema
|
|
-> boundary mapper
|
|
-> immutable application projection
|
|
```
|
|
|
|
금지:
|
|
|
|
```text
|
|
page/use-case -> generated service client
|
|
page/use-case -> protobuf message or descriptor
|
|
page/use-case -> transport/base URL/metadata
|
|
generated message -> TanStack cache
|
|
raw Connect/gRPC error -> presentation
|
|
REST gateway DTO -> domain without runtime schema/mapper
|
|
```
|
|
|
|
application port는 `listResources`, `createResource`, `watchJob` 같은 의미를
|
|
표현한다. `callRpc(service, method, bytes)`나 `executeProto<T>()`를 노출하지 않는다.
|
|
|
|
## 5. Protocol-neutral operation과 exact binding
|
|
|
|
```text
|
|
ApiOperationContractV3
|
|
semanticOperationId
|
|
owner
|
|
semantics = QUERY | COMMAND | SERVER_STREAM
|
|
protocol = REST | CONNECT_HTTP | GRPC_WEB
|
|
replayPolicy
|
|
idempotencyKeyPolicy
|
|
authProfileId
|
|
csrfProfileId
|
|
deadlineProfileId
|
|
retryProfileId
|
|
requestSemanticSchemaId
|
|
responseSemanticSchemaId
|
|
mapperId
|
|
serverStateProfileId | null
|
|
dataClassification
|
|
compatibility
|
|
protocolBinding
|
|
```
|
|
|
|
현재 source에서는 설치된 REST V2 registry를 위험한 union migration으로 바꾸지
|
|
않고 `BrowserRpcOperationV3` sibling registry를 추가했다. 제품 operation이
|
|
선택되면 composition root가 semantic feature gateway 뒤에서 REST 또는 Browser
|
|
RPC 중 정확히 하나를 bind한다. use-case가 두 registry를 보거나 runtime fallback을
|
|
결정하지 않는다.
|
|
|
|
Connect/gRPC-Web binding:
|
|
|
|
```text
|
|
BrowserRpcOperationV3 + BrowserRpcProviderProfile
|
|
providerId
|
|
clientRuntimeId
|
|
clientRuntimeVersion
|
|
transportRuntimeKind = CONNECT_WEB_FETCH | OFFICIAL_GRPC_WEB_XHR | CUSTOM_FETCH_FRAMED
|
|
clientApiKind = PROMISE | ASYNC_ITERABLE | CALLBACK_STREAM
|
|
protocolRevision
|
|
wireProfileId
|
|
encoding = PROTO_BINARY | PROTO_JSON
|
|
fullyQualifiedService
|
|
method
|
|
rpcKind = UNARY | SERVER_STREAM
|
|
requestMessageId
|
|
responseMessageId
|
|
descriptorArtifactId
|
|
descriptorDigest
|
|
errorProfileId
|
|
corsProfileId
|
|
compressionProfileId
|
|
deadlineDialect
|
|
retryOwner = FRONTEND_ADAPTER | EDGE_PROXY | NONE
|
|
maxRequestBytes
|
|
maxHeaderBytes
|
|
maxHeaderFields
|
|
maxMessageBytes
|
|
maxResponseMessages
|
|
maxTotalResponseBytes
|
|
idleDeadlineMs | null
|
|
```
|
|
|
|
구현 경로는 `src/contracts/browser-rpc.ts`,
|
|
`src/application/ports/browser-rpc`, `src/adapters/browser-rpc`다. 공통 profile은
|
|
runtime identity/digest, fixed base URL, descriptor digest, allowed procedure,
|
|
auth/CSRF/CORS/error/deadline/retry owner와 raw-byte ceiling owner까지 고정한다.
|
|
`maxHeaderBytes`, media/trailer parsing과 실제 raw queue ceiling은 공통 coordinator가
|
|
추측하지 않고 selected wire transport가 VD-27/VD-29에 따라 추가로 집행한다.
|
|
|
|
REST Gateway binding:
|
|
|
|
```text
|
|
RestGatewayBindingV1
|
|
providerId
|
|
gatewayKind = CURATED_BFF | GRPC_GATEWAY | ENVOY_TRANSCODER
|
|
gatewayRuntimeId
|
|
gatewayRuntimeVersion
|
|
httpRuleArtifactId
|
|
httpRuleDigest
|
|
method
|
|
relativePathTemplate
|
|
requestProjection
|
|
protoJsonProfileId | null
|
|
responseEnvelopeProfileId
|
|
statusErrorProfileId
|
|
cacheConditionalProfileId
|
|
```
|
|
|
|
registry는 다음을 boot/build 전에 거절한다.
|
|
|
|
- protocol/profile/runtime/provider tuple 불일치
|
|
- descriptor 또는 HTTP rule digest 불일치
|
|
- caller-provided endpoint, method, metadata 또는 message type
|
|
- `SERVER_STREAM`인데 ordinary Query profile 사용
|
|
- browser client/bidi method
|
|
- Connect와 gRPC-Web decoder의 runtime auto-negotiation
|
|
- runtime kind와 deadline/cancel/status API가 맞지 않는 client API 조합
|
|
- frontend와 edge proxy가 동시에 retry owner인 profile
|
|
- gateway kind가 다른 두 route의 last-write-wins collision
|
|
- JSON exposure인데 binary-only compatibility gate만 통과
|
|
- non-replayable command retry/fallback
|
|
- hard ceiling보다 큰 message/frame/deadline/URL
|
|
|
|
## 6. Protobuf contract source
|
|
|
|
### 6.1 Source of truth
|
|
|
|
```text
|
|
authenticated Proto/Buf module
|
|
-> immutable source commit/module digest
|
|
-> lint
|
|
-> breaking comparison
|
|
-> FileDescriptorSet
|
|
-> deterministic codegen
|
|
-> generated artifact digest
|
|
-> operation/schema/mapper binding
|
|
-> release contract set
|
|
```
|
|
|
|
runtime endpoint에서 latest schema를 받아 build하지 않는다. schema update는
|
|
review 가능한 explicit workflow이며 source, dependency lock, descriptor,
|
|
plugin/runtime와 generated output digest를 함께 보존한다.
|
|
|
|
### 6.2 Evolution
|
|
|
|
- field number 재사용/renumber 금지
|
|
- 삭제 field number와 JSON name reserve
|
|
- package/service/method full name 안정성
|
|
- enum zero value와 unknown numeric policy
|
|
- proto3 optional/edition presence를 명시
|
|
- oneof absence/unknown case 처리
|
|
- map ordering을 identity/digest로 사용하지 않음
|
|
- unknown field가 binary↔JSON conversion에서 보존되지 않을 수 있음을 반영
|
|
- Timestamp/Duration range와 nanos
|
|
- int64/uint64의 JS/ProtoJSON projection
|
|
- bytes와 repeated/nesting ceiling
|
|
|
|
JSON을 한 곳이라도 사용하면 binary wire compatibility만으로 충분하지 않다.
|
|
최소 `WIRE_JSON` breaking category를 요구한다. generated SDK의 import/source
|
|
compatibility를 외부 소비자가 의존하면 `PACKAGE` 또는 `FILE`을 선택한다.
|
|
Buf gate와 별도로 domain meaning, authorization, default/presence, pagination,
|
|
revision과 idempotency semantic diff를 수행한다.
|
|
|
|
### 6.3 ProtoJSON profile
|
|
|
|
operation은 다음을 고정한다.
|
|
|
|
```text
|
|
ProtoJsonProfileV1
|
|
emitDefaultValues
|
|
useProtoFieldNames
|
|
enumEncoding = NAME | NUMBER
|
|
ignoreUnknownFields
|
|
int64Projection
|
|
bytesProjection
|
|
wellKnownTypePolicy
|
|
```
|
|
|
|
runtime/library default에 맡기지 않는다. 기본 방향:
|
|
|
|
- request unknown field reject
|
|
- response는 generated decoder 뒤 known semantic projection만 mapper에 전달
|
|
- int64/uint64는 decimal string 또는 bounded application type
|
|
- bytes는 base64 decode 전후 byte ceiling
|
|
- non-finite float는 domain 승인 없으면 거절
|
|
- Timestamp/Duration은 generated decode 성공 뒤 semantic range 재검증
|
|
- `Any`는 allowlisted type URL만
|
|
|
|
ProtoJSON은 ordinary JSON schema의 임의 union을 대체하지 않으며 binary보다
|
|
evolution 보장이 약하다.
|
|
|
|
## 7. Deterministic codegen과 공급망
|
|
|
|
선택 branch는 최소 다음을 고정한다.
|
|
|
|
```text
|
|
buf.yaml
|
|
buf.lock
|
|
buf.gen.yaml
|
|
schema/module digest
|
|
protoc or Buf version
|
|
plugin name/version/revision/digest
|
|
@bufbuild/protobuf version
|
|
@connectrpc/connect version
|
|
@connectrpc/connect-web version
|
|
Buf image digest
|
|
FileDescriptorSet digest
|
|
canonical HttpRule manifest digest
|
|
generated OpenAPI digest when selected
|
|
generated output digest
|
|
```
|
|
|
|
CI:
|
|
|
|
- format/lint/breaking
|
|
- dependency lock과 source provenance
|
|
- clean checkout generate diff 0
|
|
- generated directory 수동 수정 금지
|
|
- generated import boundary
|
|
- descriptor↔generated symbol exact join
|
|
- generated runtime↔runtime compatibility matrix
|
|
- canonical HttpRule/OpenAPI semantic diff; custom option을 Buf breaking에 위임하지 않음
|
|
- N/N-1 fixture
|
|
- license/SBOM/vulnerability/secret scan
|
|
- production bundle inventory와 budget
|
|
- capability removal 뒤 generated/runtime/proxy reference 0
|
|
|
|
generated files는 `src/generated` 같은 전역 public API가 아니라 selected
|
|
feature adapter-private root에 둔다. 여러 feature가 공유하는 service라도 좁은
|
|
platform adapter facade만 재사용한다.
|
|
|
|
## 8. Connect-Web
|
|
|
|
Connect-Web은 browser client runtime이다. `createConnectTransport()`는 Connect
|
|
protocol, `createGrpcWebTransport()`는 gRPC-Web protocol을 사용한다. 같은 package를
|
|
쓴다고 두 wire protocol이 호환되거나 자동 failover 가능한 것은 아니다.
|
|
Connect-ES v2 generation은 `protoc-gen-es`가 message와 service descriptor를
|
|
함께 생성하며 과거 Connect 전용 generator를 신규 toolchain에 넣지 않는다.
|
|
|
|
### 8.1 Connect unary
|
|
|
|
```text
|
|
validated semantic input
|
|
-> generated request
|
|
-> bounded encode (ProtoJSON or binary)
|
|
-> POST fixed /package.Service/Method
|
|
-> bounded response/error decode
|
|
-> generated message
|
|
-> semantic validation
|
|
-> mapper
|
|
-> generation fence
|
|
```
|
|
|
|
Connect unary는 bare Protobuf/JSON body와 의미 있는 HTTP status를 사용한다.
|
|
success content type과 selected encoding을 exact 검증한다. error는 non-2xx JSON
|
|
Connect error profile로만 decode하며 intermediary HTML/JSON을 Connect error로
|
|
추측하지 않는다.
|
|
|
|
stock runtime이 unary body를 whole JSON/ArrayBuffer로 읽는 selected version이면
|
|
interceptor가 raw-byte ceiling을 대신한다고 기록하지 않는다. edge의 encoded/
|
|
decompressed cap과 platform-owned bounded Fetch transport 또는 해당 exact
|
|
runtime의 overflow conformance가 있어야 production evidence가 닫힌다.
|
|
|
|
GET은 다음을 모두 만족할 때만 별도 profile로 허용한다.
|
|
|
|
- protobuf method `idempotency_level = NO_SIDE_EFFECTS`
|
|
- public/non-sensitive bounded request
|
|
- URL byte ceiling과 canonical encoding
|
|
- header/credential/preflight 정책
|
|
- exact CDN/browser cache key, `Vary`, ETag와 retention
|
|
- request URL이 log/history/referrer에 노출돼도 허용되는 classification
|
|
|
|
인증/private query의 기본은 POST다.
|
|
|
|
### 8.2 Connect server stream
|
|
|
|
Connect streaming은 framed `application/connect+proto|json`이고 HTTP status
|
|
`200` 안의 final EndStream envelope가 RPC error/trailer authority다.
|
|
|
|
decoder는:
|
|
|
|
- incremental 5-byte envelope prefix
|
|
- flag/compression/declared length
|
|
- per-message/decompressed/total/count ceiling
|
|
- exactly one final EndStream envelope
|
|
- data after EndStream, missing EndStream와 truncation
|
|
- bounded error/detail/trailer projection
|
|
- idle + total deadline
|
|
- reader cancel/release와 bounded consumer queue
|
|
|
|
를 검증한다. network EOF는 success가 아니다. stock Connect-Web runtime을
|
|
사용하면 exact package version이 이 state machine을 얼마나 집행하는지
|
|
conformance로 증명하고, 부족한 hard ceiling은 proxy 또는 custom Transport가
|
|
소유한다. selected stock runtime의 streaming output compression은
|
|
`IDENTITY_ONLY`로 고정하고 지원하지 않는 compressed envelope를 광고하지 않는다.
|
|
|
|
### 8.3 Interceptor policy
|
|
|
|
interceptor가 허용되는 책임:
|
|
|
|
- registry-owned auth/CSRF patch
|
|
- remaining deadline/timeout
|
|
- fixed low-cardinality diagnostics
|
|
- exact safe retry coordinator
|
|
- safe error normalization
|
|
|
|
금지:
|
|
|
|
- arbitrary URL/service/method rewrite
|
|
- raw message/error logging
|
|
- caller metadata passthrough
|
|
- operation semantic retry를 transport가 임의 결정
|
|
- cache write와 domain mapping
|
|
|
|
transport/client는 runtime/scope 단위로 재사용하되 base URL/profile이 다른
|
|
provider 사이에서 공유하지 않는다.
|
|
|
|
interceptor array의 선언 순서가 아니라 실제 onion execution order를 manifest와
|
|
test에 보존한다. remaining total deadline이 0 이하면 `timeoutMs=0`을 넘기지 않고
|
|
호출 전에 local deadline failure로 닫는다.
|
|
|
|
## 9. gRPC-Web
|
|
|
|
상세 frame/status state machine은 VD-27을 따른다.
|
|
|
|
- browser는 native gRPC/HTTP2 transport를 직접 사용한다고 가정하지 않는다.
|
|
- official `grpc-web` runtime은 XHR와 runtime-owned frame/status decoder,
|
|
`ClientReadableStream.cancel()`을 사용한다. raw `ReadableStream` frame parser와
|
|
`AbortSignal`을 이 경로의 frontend 보장으로 기록하지 않는다.
|
|
- custom Fetch runtime만 `fetch`/`AbortController`/incremental raw-frame decoder
|
|
profile을 사용한다. 두 runtime은 `transportRuntimeKind`로 분리한다.
|
|
- gRPC-Web response trailer는 body trailer frame 또는 trailers-only response
|
|
header에서 해석한다.
|
|
- `grpc-status`가 있으면 그것이 authoritative다. 없으면 native gRPC의 공식
|
|
HTTP→gRPC fallback mapping으로 internal status를 만들며 HTTP success만으로
|
|
RPC success를 판정하지 않는다.
|
|
- official `grpc-web` JavaScript runtime은 binary unary와 text unary/server
|
|
streaming capability를 구분한다.
|
|
- Connect-Web gRPC-Web transport를 선택하면 별도 `clientRuntimeId`와 그 runtime의
|
|
conformance matrix를 사용한다.
|
|
- text/base64와 binary를 같은 byte budget으로 취급하지 않는다.
|
|
- text decoder는 browser chunk 하나를 base64 entity 하나로 가정하지 않고,
|
|
중간 padding이 있는 연속 base64 entity를 처리해야 한다.
|
|
- Envoy `grpc_web` filter 또는 selected gateway의 exact version/config를
|
|
provider profile에 binding한다.
|
|
- Envoy profile은 filter order, upstream HTTP/2, route/idle/max-stream timeout,
|
|
gRPC timeout offset, buffering/flush, header/message ceiling과 local reply
|
|
mapping을 고정한다. server stream에는 default route timeout을 그대로 쓰지 않는다.
|
|
- retry owner는 정확히 하나다. server stream replay/hedge는 금지하고, frontend와
|
|
Envoy retry를 동시에 켜지 않는다.
|
|
|
|
gRPC-Web과 Connect stream은 terminal envelope가 서로 다르다. 공통
|
|
`ReadableStream` helper를 재사용할 수 있어도 decoder/state machine을 합치지 않는다.
|
|
|
|
## 10. REST Gateway
|
|
|
|
### 10.1 세 종류를 분리한다
|
|
|
|
`CURATED_BFF`
|
|
|
|
- browser/resource contract를 별도로 설계
|
|
- REST envelope, status, ETag/304/412, idempotency, pagination과 CORS를 직접 소유
|
|
- 내부에서 gRPC/Connect service를 호출할 수 있으나 public DTO는 별도 mapper
|
|
|
|
`GRPC_GATEWAY`
|
|
|
|
- `google.api.http` annotation에서 reverse proxy와 optional OpenAPI를 생성
|
|
- proto request field를 path/query/body로 projection
|
|
- ProtoJSON과 gRPC status mapping을 exact profile로 고정
|
|
- initial reference 후보는 unary만이며 REST streaming은 별도 ADR 없이는 선택하지 않음
|
|
|
|
`ENVOY_TRANSCODER`
|
|
|
|
- descriptor와 `google.api.http` annotation으로 proxy filter가 JSON↔gRPC 변환
|
|
- filter/runtime config와 descriptor를 coherent artifact로 배포
|
|
- application-specific envelope/cache/idempotency 의미는 별도 filter/BFF 없이는
|
|
자동 생성되지 않음
|
|
|
|
한 route는 하나의 gateway kind만 소유한다.
|
|
|
|
현재 reference REST의 `{ success, data|error, meta }` envelope와
|
|
`200|201`, 향후 `204/304/412` 의미는 direct generated gateway의 기본 출력과
|
|
같지 않다. 따라서 기존 reference operation은 curated BFF를 유지한다. direct
|
|
gateway는 ProtoJSON/HTTP rule/status/error 자체를 새 versioned public contract로
|
|
승인한 operation에만 적용한다.
|
|
|
|
### 10.2 Automatic transcoding의 한계
|
|
|
|
HTTP annotation은 path/method/body projection을 정의하지만 다음을 자동으로
|
|
완성하지 않는다.
|
|
|
|
- frontend의 strict success/failure envelope
|
|
- product authorization와 existence hiding
|
|
- idempotency store/effect certainty
|
|
- CursorPage snapshot 의미
|
|
- ETag/If-None-Match/If-Match와 revision CAS
|
|
- CDN/private cache policy
|
|
- domain error vocabulary와 validation detail redaction
|
|
- file Range/streaming download/upload
|
|
- browser-compatible server event/reconnect protocol
|
|
- OpenAPI와 runtime response/error rewrite의 자동 coherence
|
|
|
|
따라서 현재 reference REST envelope에 generated gateway를 바로 연결할 수 없다.
|
|
gateway adapter가 exact envelope/status를 제공하거나 frontend에 새 operation
|
|
contract를 versioned로 추가해야 한다.
|
|
|
|
### 10.3 REST mapping
|
|
|
|
- resource name/path는 stable API meaning을 가져야 한다.
|
|
- path field를 body/query에도 중복 projection하지 않는다.
|
|
- unbound fields의 query mapping과 repeated/nested encoding을 fixture로 고정한다.
|
|
- `body: "*"`는 query surface와 HTTP semantics를 숨길 수 있어 default 금지다.
|
|
- additional binding collision과 ambiguous path template를 build에서 거절한다.
|
|
- `.proto` annotation을 기본 source of truth로 삼고 external service config를
|
|
병용하면 override precedence와 두 artifact digest를 고정한다.
|
|
- `generate_unbound_methods`는 기본 금지하며 GET/DELETE body, path field type,
|
|
path unescape mode와 PATCH FieldMask behavior를 fixture로 고정한다.
|
|
- Buf generic breaking check가 custom HTTP option의 제품 의미까지 증명한다고
|
|
간주하지 않는다. canonical route manifest와 generated OpenAPI의 method/path/
|
|
body/query/additional-binding diff를 별도 gate로 검사한다.
|
|
- `response_body` projection은 전체 response schema와 mapper binding을 별도로
|
|
갖는다.
|
|
- ProtoJSON JSON name/default/enum/int64/unknown policy를 고정한다.
|
|
- request/response body와 URL ceiling은 transcoder 앞/뒤 모두 적용한다.
|
|
|
|
server-stream을 JSON array로 buffer해 반환하는 transcoder 동작을 realtime
|
|
stream으로 간주하지 않는다. SSE/NDJSON/streaming JSON이 필요하면 별도 protocol
|
|
ADR과 framing/content type/terminal contract를 만든다.
|
|
|
|
## 11. Auth, CSRF와 CORS
|
|
|
|
same-origin BFF를 권장한다. cross-origin이면 protocol별 exact profile이 필요하다.
|
|
|
|
Connect:
|
|
|
|
- POST와 optional GET
|
|
- `Content-Type`, `Connect-Protocol-Version`, `Connect-Timeout-Ms`,
|
|
selected compression/auth headers
|
|
- custom unary trailer를 expose할 때 `Trailer-<Name>`
|
|
|
|
gRPC-Web:
|
|
|
|
- POST
|
|
- `Content-Type`, `Grpc-Timeout`, `X-Grpc-Web`, `X-User-Agent`,
|
|
selected auth headers
|
|
- `Grpc-Status`, `Grpc-Message`, `Grpc-Status-Details-Bin` expose
|
|
|
|
REST Gateway:
|
|
|
|
- operation별 method/header/media
|
|
- bearer 또는 cookie+CSRF profile
|
|
- conditional/idempotency header allow/expose
|
|
|
|
공통:
|
|
|
|
- exact allow-origin, credential mode와 `Vary`
|
|
- wildcard credential 금지
|
|
- redirect login/HTML response 금지
|
|
- Origin/Fetch Metadata/CSRF를 cookie unsafe method에서 검증
|
|
- generated client/caller가 arbitrary metadata를 제출하지 못함
|
|
- auth attach 뒤 endpoint/path/method/body digest 불변
|
|
|
|
## 12. Deadline, cancellation, retry와 command
|
|
|
|
```text
|
|
total logical deadline
|
|
= auth attach
|
|
+ transport attempts/backoff
|
|
+ body/frame read/decompression
|
|
+ generated decode
|
|
+ semantic validation
|
|
+ mapper
|
|
```
|
|
|
|
- Connect-Web call은 AbortSignal과 bounded timeout을 받는다.
|
|
- local deadline과 wire timeout 중 더 짧은 값만 사용한다.
|
|
- stream은 idle/total deadline 둘 다 갖는다.
|
|
- abort가 backend command rollback을 의미하지 않는다.
|
|
- generated/runtime interceptor retry와 Query retry를 중복하지 않는다.
|
|
- `SAFE | IDEMPOTENT | KEYED_COMMAND`의 exact evidence가 있는 operation만 retry한다.
|
|
- keyed command는 protocol과 무관하게 backend atomic idempotency/reconcile이
|
|
필요하다.
|
|
- gateway의 header/message mapping은 idempotency key를 전달할 뿐 durable
|
|
dedupe, receipt, retention과 reconcile을 구현하지 않는다.
|
|
- browser abort는 이미 commit된 command의 rollback 증거가 아니다. edge→upstream
|
|
cancel/deadline 전파와 backend cooperative cancellation을 staging에서 검증한다.
|
|
- protocol 장애를 이유로 Connect↔gRPC-Web↔REST command를 자동 replay하지 않는다.
|
|
- read fallback도 새 logical operation으로 시작하고 old result를 cache에 쓰지 않는다.
|
|
|
|
## 13. Status와 error mapping
|
|
|
|
common safe vocabulary에 mapping하되 wire authority를 섞지 않는다.
|
|
|
|
| source | authoritative outcome |
|
|
| --- | --- |
|
|
| Connect unary | HTTP status + bounded Connect JSON error |
|
|
| Connect stream | HTTP admission + final EndStream envelope |
|
|
| gRPC-Web | HTTP admission + terminal grpc-status source |
|
|
| REST Gateway | selected HTTP/envelope/problem profile |
|
|
|
|
mapping은 `UNAUTHENTICATED/AUTH_REQUIRED`, `PERMISSION_DENIED/FORBIDDEN`,
|
|
`NOT_FOUND`, `CONFLICT/ABORTED`, validation, rate limit, unavailable,
|
|
deadline/cancel을 공통 `AppFailure`로 투영한다. raw message, arbitrary Any/detail,
|
|
metadata/trailer와 vendor error는 application/log에 전달하지 않는다.
|
|
|
|
같은 semantic backend error라도 transport별 status body가 다를 수 있으므로
|
|
conformance suite가 최종 `AppFailure`와 retry/effect certainty가 같은지 검증한다.
|
|
|
|
## 14. Server State와 cache
|
|
|
|
- query key는 semantic application input에서만 파생한다.
|
|
- service/method/protobuf bytes/REST URL을 key에 넣지 않는다.
|
|
- generated message/Connect response를 cache하지 않고 mapper output만 admission한다.
|
|
- transport retry가 있으면 Query retry는 off다.
|
|
- scope/generation mismatch result는 폐기한다.
|
|
- Connect GET/CDN cache, browser HTTP cache와 TanStack cache owner를 operation별로
|
|
하나씩 명시한다.
|
|
- stream event는 ordinary query result가 아니다.
|
|
- finite stream aggregate는 terminal success 뒤 atomic commit한다.
|
|
- long-running stream은 bounded reducer 또는 invalidate-only hint를 사용한다.
|
|
|
|
## 15. Security와 privacy
|
|
|
|
- descriptor/generated code는 신뢰된 source에서만
|
|
- fixed provider/service/method/route
|
|
- message/body/frame/decompressed/collection/depth ceiling
|
|
- recursive schema와 Any type allowlist
|
|
- frontend generated validation을 backend authorization으로 간주 금지
|
|
- raw payload, ProtoJSON, debug stringifier, metadata/trailer/cursor/revision log 금지
|
|
- request/response message에 credential을 넣지 않음
|
|
- query cache/persistence에 generated message 없음
|
|
- gateway가 unknown field/duplicate JSON key를 어떻게 처리하는지 fixture로 고정
|
|
|
|
## 16. Observability
|
|
|
|
허용:
|
|
|
|
```text
|
|
semantic operation ID
|
|
protocol/client runtime/provider profile ID
|
|
descriptor/http-rule artifact version
|
|
HTTP status group / safe RPC code
|
|
attempt/duration/message-size/count bucket
|
|
stream terminal/idle/gap/overflow bucket
|
|
cache admission outcome
|
|
```
|
|
|
|
금지:
|
|
|
|
- service request/response content
|
|
- protobuf debug JSON/string
|
|
- metadata/trailer/error message/detail
|
|
- URL query/GET message
|
|
- credential/idempotency/cursor/revision
|
|
- resource/account identifier actual value
|
|
|
|
protocol별 metric을 비교할 때 semantic operation ID를 join key로 쓰고 raw
|
|
service/method를 high-cardinality label로 사용하지 않는다.
|
|
|
|
## 17. Test와 provider evidence
|
|
|
|
### 17.1 Deterministic
|
|
|
|
- proto format/lint/breaking/descriptor/codegen digest
|
|
- enum/oneof/presence/int64/time/bytes/unknown-field fixture
|
|
- Connect unary JSON/binary success/error/media/status
|
|
- Connect stream partial prefix, length, compression, EndStream, truncation
|
|
- gRPC-Web binary/text frame/trailer/status matrix
|
|
- REST annotation path/query/body/response mapping
|
|
- ProtoJSON default/name/enum/int64/null/unknown behavior
|
|
- retry/deadline/cancel/auth/idempotency
|
|
- scope/generation/cache admission
|
|
- generated import/bundle/removal
|
|
|
|
### 17.2 Actual provider/browser
|
|
|
|
- selected Connect/gRPC-Web server or Envoy/gateway version
|
|
- exact CORS/preflight/auth/CSRF
|
|
- HTTP/1.1/2 proxy buffering and stream flush
|
|
- media, compression, terminal status/trailer preservation
|
|
- message/body/time limit
|
|
- Chromium/Firefox/WebKit cancel/stream/backpressure
|
|
- REST Gateway N/N-1 and OpenAPI/descriptor coherence
|
|
- browser abort→gateway context→upstream cancel/deadline propagation
|
|
- HttpRule route manifest/OpenAPI/runtime response rewrite coherence
|
|
- kill switch, rollback and dependency/proxy removal drill
|
|
|
|
memory fake/MSW만으로 provider conformance를 주장하지 않는다.
|
|
|
|
## 18. Rollout
|
|
|
|
```text
|
|
product operation selected
|
|
-> schema/provider/gateway owner
|
|
-> immutable proto + descriptor
|
|
-> codegen and semantic mapper
|
|
-> provider-neutral adapter/fake
|
|
-> actual gateway/browser conformance
|
|
-> AVAILABLE_NOT_COMPOSED
|
|
-> bootstrap TrafficAdmission=DISABLED
|
|
-> COMPOSED
|
|
-> read-only shadow
|
|
-> canary
|
|
-> enabled
|
|
```
|
|
|
|
- 처음에는 safe unary read 하나만 선택한다.
|
|
- shadow result는 UI/cache에 쓰지 않는다.
|
|
- Connect와 gRPC-Web을 동시에 canary하지 않는다.
|
|
- server stream은 unary와 별도 gate다.
|
|
- command는 backend idempotency/reconcile 뒤 별도 gate다.
|
|
- REST fallback은 사전 등록된 read operation만 새 logical query로 실행한다.
|
|
- rollback은 frontend/generated descriptor/gateway/backend를 coherent set으로 한다.
|
|
|
|
## 19. Removal
|
|
|
|
1. 신규 call/stream admission을 닫는다.
|
|
2. query/stream cancel, command effect reconcile.
|
|
3. cache/reducer/invalidation listener를 clear한다.
|
|
4. operation/schema/mapper/provider profile을 제거한다.
|
|
5. generated source, descriptor/proto input과 codegen config를 제거한다.
|
|
6. Connect/gRPC runtime dependency와 proxy/transcoder route를 제거한다.
|
|
7. backend method/HTTP binding은 N/N-1 client window 뒤 retirement한다.
|
|
8. bundle, SBOM, lockfile, proxy config와 source reference 0을 증명한다.
|
|
|
|
## 20. 구현 work package
|
|
|
|
| 순서 | package | exit |
|
|
| --- | --- | --- |
|
|
| PB-01 | schema governance | authenticated module, Buf lint/breaking, descriptor/digest |
|
|
| PB-02 | generated boundary | deterministic TS generation, private imports, mapper |
|
|
| PB-03 | Connect unary | exact JSON/binary profile, error/deadline/cancel |
|
|
| PB-04 | gRPC-Web unary | selected runtime/proxy/frame/status conformance |
|
|
| PB-05 | bounded server stream | queue/idle/total/terminal/sequence/resume |
|
|
| PB-06 | REST Gateway | selected kind, HTTP annotation/ProtoJSON/error/cache fixture |
|
|
| PB-07 | operations | browser/provider evidence, canary/kill/rollback/removal |
|
|
|
|
PB-03~06은 제품에서 선택한 branch만 구현한다. “미래 대비”로 모두 설치하지 않는다.
|
|
|
|
## 21. 완료 기준
|
|
|
|
- [ ] Protobuf, client runtime, wire protocol과 gateway 축이 분리돼 있다.
|
|
- [ ] 한 operation은 한 active transport/provider profile만 가진다.
|
|
- [ ] descriptor/codegen/runtime/gateway artifact가 release digest에 binding된다.
|
|
- [ ] generated type이 adapter 밖으로 나오지 않는다.
|
|
- [ ] ProtoJSON과 binary compatibility policy가 각각 닫혀 있다.
|
|
- [ ] Connect unary/stream과 gRPC-Web decoder가 서로의 terminal 규칙을 섞지 않는다.
|
|
- [ ] REST Gateway가 envelope/cache/idempotency를 자동 제공한다고 가장하지 않는다.
|
|
- [ ] client/bidi streaming을 browser 공통 capability로 표시하지 않는다.
|
|
- [ ] actual proxy와 세 browser evidence가 있다.
|
|
- [ ] command fallback/replay가 backend effect certainty를 우회하지 않는다.
|
|
- [ ] coherent rollback과 complete removal drill이 통과한다.
|
|
|
|
## 22. 규범·공식 근거
|
|
|
|
- [Connect protocol reference](https://connectrpc.com/docs/protocol/)
|
|
- [Connect-Web protocol selection](https://connectrpc.com/docs/web/choosing-a-protocol/)
|
|
- [Connect-Web code generation](https://connectrpc.com/docs/web/generating-code/)
|
|
- [Connect and gRPC-Web CORS](https://connectrpc.com/docs/cors/)
|
|
- [gRPC-Web protocol delta](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md)
|
|
- [Official grpc-web runtime](https://github.com/grpc/grpc-web)
|
|
- [Envoy gRPC-Web filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_web_filter.html)
|
|
- [gRPC HTTP status fallback mapping](https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md)
|
|
- [Envoy timeout configuration](https://www.envoyproxy.io/docs/envoy/latest/faq/configuration/timeouts.html)
|
|
- [Protocol Buffers language guide](https://protobuf.dev/programming-guides/proto3/)
|
|
- [ProtoJSON format](https://protobuf.dev/programming-guides/json/)
|
|
- [Buf breaking changes](https://buf.build/docs/breaking/)
|
|
- [gRPC-Gateway introduction](https://grpc-ecosystem.github.io/grpc-gateway/docs/tutorials/introduction/)
|
|
- [gRPC-Gateway customization](https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/customizing_your_gateway/)
|
|
- [Google API HTTP annotation](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto)
|
|
- [Envoy gRPC-JSON transcoder](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_json_transcoder_filter)
|