chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,775 @@
|
||||
# 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)
|
||||
|
||||
## 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)
|
||||
Reference in New Issue
Block a user