---
id: wiki-2026-0508-distributed-system-type-safety
title: Distributed System Type Safety
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [cross-service-types, contract-types, schema-driven-types]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [distributed-systems, type-safety, contracts, codegen]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: typescript
  framework: gRPC/OpenAPI/tRPC
---

# Distributed System Type Safety

## 매 한 줄
> **"매 service boundary 가 매 type system 의 chasm"**. 매 single-process compile-time check 가 매 network call 에서 evaporate — 매 schema-first contract (Protobuf, OpenAPI, GraphQL SDL, Avro) + codegen 의 매 cross-service guarantee. 2026 의 매 Buf + Connect, tRPC over WebSocket, GraphQL Federation v3, gRPC + gRPC-Web 가 매 dominant choices.

## 매 핵심

### 매 problem
- 매 service A 의 type 변경 → 매 service B 가 silently break.
- 매 JSON 의 매 stringly-typed wire — 매 runtime error.
- 매 versioning 가 매 hard.

### 매 solution categories
1. **Schema-first + codegen**: gRPC/Protobuf, OpenAPI, AsyncAPI.
2. **TypeScript-end-to-end**: tRPC, ts-rest, Encore.
3. **Federated graph**: GraphQL Federation v3.
4. **Event schema registry**: Confluent Schema Registry (Avro/Protobuf), AWS Glue.
5. **CRDT + typed sync**: Yjs + typed shape.

### 매 verification levels
- **Compile time** — codegen output type-checked.
- **Build time** — schema lint + breaking-change detection (Buf).
- **CI** — contract tests (Pact).
- **Runtime** — schema validation at boundary (Zod, Pydantic).

## 💻 패턴

### Buf + Protobuf workflow
```bash
# buf.yaml
version: v2
modules: [{ path: proto }]
breaking: { use: [FILE] }
lint: { use: [STANDARD] }

# CI 의 breaking-change check
buf breaking --against '.git#branch=main'
buf generate
```

```protobuf
// proto/order/v1/order.proto
syntax = "proto3";
package order.v1;
message Order {
  string id = 1;
  string customer_id = 2;
  repeated LineItem items = 3;
  Money total = 4;
}
service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc StreamOrders(StreamOrdersRequest) returns (stream Order);
}
```

### Connect-RPC (HTTP+JSON+Protobuf, browser-friendly)
```ts
import { createPromiseClient } from "@connectrpc/connect";
import { OrderService } from "./gen/order/v1/order_connect";
const client = createPromiseClient(OrderService, transport);
const order = await client.getOrder({ id: "o_123" });
// 매 fully typed end-to-end.
```

### tRPC v11 (TypeScript-only stacks)
```ts
// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
export const appRouter = t.router({
  order: t.router({
    get: t.procedure.input(z.object({ id: z.string() })).query(({ input }) => db.order.find(input.id)),
  }),
});
export type AppRouter = typeof appRouter;

// client.ts — 매 zero codegen, 매 type 의 inference
import type { AppRouter } from '../server/router';
const trpc = createTRPCClient<AppRouter>({ links: [...] });
const order = await trpc.order.get.query({ id: 'o_123' });
```

### OpenAPI → typed client (orval / openapi-fetch)
```ts
// 매 openapi-fetch 의 zero-runtime-overhead typed fetch
import createClient from 'openapi-fetch';
import type { paths } from './gen/api';
const api = createClient<paths>({ baseUrl: 'https://api.example.com' });
const { data, error } = await api.GET('/orders/{id}', { params: { path: { id: 'o_123' } } });
```

### GraphQL Federation v3 + codegen
```graphql
# orders subgraph
type Order @key(fields: "id") {
  id: ID!
  customerId: ID!
  total: Money!
}
extend type Customer @key(fields: "id") {
  id: ID!
  orders: [Order!]! @requires(fields: "id")
}
```

```bash
graphql-codegen --config codegen.ts
# 매 typed hooks (useOrderQuery) generated.
```

### Avro schema registry (Kafka)
```json
{
  "type": "record",
  "name": "OrderPlaced",
  "namespace": "com.acme.order.v1",
  "fields": [
    {"name": "orderId", "type": "string"},
    {"name": "customerId", "type": "string"},
    {"name": "totalCents", "type": "long"},
    {"name": "occurredAt", "type": {"type": "long", "logicalType": "timestamp-millis"}}
  ]
}
```

```ts
import { SchemaRegistry, AvroKafka } from '@kafkajs/confluent-schema-registry';
const registry = new SchemaRegistry({ host: 'http://schema-registry:8081' });
const id = await registry.register({ type: SchemaType.AVRO, schema });
await producer.send({ topic: 'orders', messages: [{ value: await registry.encode(id, payload) }] });
```

### Pact contract test (consumer-driven)
```ts
import { PactV4, MatchersV3 } from '@pact-foundation/pact';
const pact = new PactV4({ consumer: 'web', provider: 'order-svc' });
await pact.addInteraction()
  .uponReceiving('a get order request')
  .withRequest('GET', '/orders/o_123')
  .willRespondWith(200, b => b.jsonBody({ id: MatchersV3.string('o_123'), total: MatchersV3.integer(100) }))
  .executeTest(async (mockServer) => { /* call client against mockServer.url */ });
```

### Runtime boundary validation
```ts
import { z } from 'zod';
const OrderDTO = z.object({ id: z.string(), total: z.number().int().nonnegative() });
type Order = z.infer<typeof OrderDTO>;
// 매 매 untrusted input 의 parse — 매 type lies 의 catch.
```

## 매 결정 기준
| 상황 | Approach |
|---|---|
| Polyglot microservices | gRPC / Connect + Buf |
| TS-only stack | tRPC |
| Public API + many consumers | OpenAPI 3.1 + openapi-fetch |
| Many clients merging data | GraphQL Federation v3 |
| Event-driven Kafka | Avro/Protobuf + Schema Registry |
| Browser-to-server typed | Connect-Web or tRPC |

**기본값**: Buf + Connect for polyglot; tRPC for TS-only; OpenAPI 3.1 for public REST. Always 매 add Zod/runtime validation at trust boundaries.

## 🔗 Graph
- 부모: [[Distributed-Systems]] · [[API-Design]]
- 변형: [[gRPC]] · [[GraphQL]] · [[tRPC]] · [[OpenAPI]]
- 응용: [[Microservices]] · [[Event-Driven-Architecture]] · [[BFF-Pattern]]
- Adjacent: [[Schema-Registry]] · [[Pact]] · [[Zod]] · [[Buf]]

## 🤖 LLM 활용
**언제**: 매 schema migration plan, 매 breaking-change explanation, 매 codegen wiring debug, 매 contract test scaffold.
**언제 X**: 매 actual schema authority decision (architecture review). 매 wire-format performance tuning (benchmarks 사용).

## ❌ 안티패턴
- **JSON without schema**: 매 매 silent break 의 inevitable.
- **Hand-written types matching wire**: 매 drift 의 guaranteed.
- **Breaking changes 의 bump major silently**: 매 Buf breaking detector 사용.
- **No runtime validation at trust boundary**: 매 type system 의 lie.
- **Single global GraphQL gateway 의 monolith**: 매 federation 의 사용.
- **Avro 의 forward+backward 가 X**: 매 default field 의 always 의 add.

## 🧪 검증 / 중복
- Verified (Buf docs, gRPC docs, GraphQL Federation v3 spec, tRPC docs, Pact specification).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — cross-service type safety: schemas, codegen, runtime validation |
