--- id: backend-grpc-patterns title: gRPC — Proto / Streaming / 인터셉터 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [backend, grpc, protobuf, streaming, vibe-coding] tech_stack: { language: "TS / Go / Java", applicable_to: ["Backend"] } applied_in: [] aliases: [protobuf, gRPC stream, unary, server streaming, bidi, ConnectRPC] --- # gRPC > Service-to-service RPC 표준. **Proto schema → 타입 안전 코드 generate**. HTTP/2 = 한 connection 다중 stream. JSON/REST 보다 빠르고 타입 안전. 브라우저 사용은 **gRPC-Web 또는 ConnectRPC**. ## 📖 핵심 개념 - 4 RPC 종류: unary / server stream / client stream / bidi stream. - Proto3 syntax: enum / oneof / map / repeated. - Interceptor: middleware (auth, logging, retry). - Deadline: 모든 호출 timeout 명시. ## 💻 코드 패턴 ### Proto 정의 ```proto syntax = "proto3"; package user.v1; service UserService { rpc GetUser(GetUserRequest) returns (User); rpc StreamUsers(ListUsersRequest) returns (stream User); rpc CreateUsers(stream CreateUserRequest) returns (BulkResult); rpc Chat(stream ChatMessage) returns (stream ChatMessage); } message User { string id = 1; string email = 2; google.protobuf.Timestamp created_at = 3; optional string name = 4; } message GetUserRequest { string id = 1; } ``` ### Server (Node + ts-proto + @grpc/grpc-js) ```ts import * as grpc from '@grpc/grpc-js'; const server = new grpc.Server(); server.addService(UserServiceService, { getUser: async (call, cb) => { const u = await db.user.findUnique({ where: { id: call.request.id } }); if (!u) return cb({ code: grpc.status.NOT_FOUND, message: 'user' }); cb(null, u); }, streamUsers: async (call) => { const it = db.user.streamAll(); for await (const u of it) call.write(u); call.end(); }, }); server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { server.start(); }); ``` ### Client + deadline + retry ```ts const client = new UserServiceClient('localhost:50051', grpc.credentials.createInsecure()); client.getUser({ id: 'u1' }, { deadline: Date.now() + 5000 }, (err, res) => { if (err?.code === grpc.status.NOT_FOUND) ... }); ``` ### Interceptor (auth) ```ts const authInterceptor: grpc.Interceptor = (opts, nextCall) => { return new grpc.InterceptingCall(nextCall(opts), { start(metadata, listener, next) { metadata.add('authorization', `Bearer ${token}`); next(metadata, listener); }, }); }; const client = new UserServiceClient(addr, creds, { interceptors: [authInterceptor] }); ``` ### ConnectRPC (browser-friendly) ```ts // HTTP/1+JSON, HTTP/2+protobuf 다 지원. CORS 친화. import { createPromiseClient } from '@connectrpc/connect'; import { createConnectTransport } from '@connectrpc/connect-web'; const t = createConnectTransport({ baseUrl: 'https://api.example.com' }); const client = createPromiseClient(UserService, t); const u = await client.getUser({ id: 'u1' }); ``` ### Streaming (server-side) ```ts const stream = client.streamUsers({}); stream.on('data', (u) => console.log(u)); stream.on('end', () => console.log('done')); stream.on('error', (e) => console.error(e)); ``` ### Versioning ```proto package user.v1; // 새 필드는 새 number 부여. 기존 number / type 절대 변경 금지. // breaking 시 user.v2 새 패키지. ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 내부 service-to-service | gRPC | | 브라우저 호출 | ConnectRPC / gRPC-Web | | public REST API | REST or GraphQL | | 실시간 양방향 | bidi stream / WebSocket | | 부분 업데이트 | google.protobuf.FieldMask | | Batching | repeated field 또는 client stream | ## ❌ 안티패턴 - **Deadline 없음**: 영원히 hang. - **Stream cancel 안 함**: 서버 자원 소진. - **Proto field number 재사용**: prod 데이터 깨짐. - **enum 0 안 만들고 시작**: default unknown 부재. - **Optional 안 쓰고 string 빈 문자열 = null**: 모호. - **Server reflection prod 켬**: schema 노출. - **TLS 없는 prod**: token 노출. ## 🤖 LLM 활용 힌트 - Proto3 + ts-proto / buf 권장. - ConnectRPC 가 modern + browser 친화. - Deadline + Interceptor 항상. ## 🔗 관련 문서 - [[REST_API_Versioning_Strategies]] - [[GraphQL_Server_Patterns]] - [[Backend_API_Auth_Strategies]]