본문 바로가기
C.W.K.
Stream
Lesson 02 of 04 · published

REST가 아닌 선택 — gRPC, GraphQL, RPC

~10 min · epilogue, grpc, graphql, alternatives

Level 0HTTP 입문자
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"REST는 여러 아키텍처 양식 가운데 하나다. 대부분의 상황에서 충분히 유용하고 기존 인프라와 잘 맞기 때문에 널리 사용된다. 그러나 일부 작업에는 더 적합한 대안이 있으며, 통신 형태를 무시하고 REST만 고집하면 불필요한 복잡성이 생긴다."

REST보다 다른 방식이 적합한 경우

1. 타입 계약이 중요한 내부 서비스 간 트래픽 → gRPC. 직접 관리하는 두 서비스가 많은 트래픽을 주고받는다면 명확한 타입 계약, 자동 생성되는 호출 코드, 효율적인 이진 직렬화, HTTP/2 다중화가 유리하다. gRPC와 Protobuf는 이러한 요구를 일관되게 해결한다. Kubernetes와 주요 클라우드 환경의 내부 서비스 메시, 대규모 마이크로서비스 환경에서 gRPC가 자주 사용되는 이유다.

2. 다양한 클라이언트가 깊은 스키마에서 서로 다른 필드를 선택하는 경우 → GraphQL. 웹, 모바일, 파트너 앱처럼 여러 클라이언트가 제품, 리뷰, 작성자, 관련 제품 가운데 서로 다른 조합을 요구하면 REST 엔드포인트가 지나치게 늘거나 필요 이상의 데이터를 반환하기 쉽다. GraphQL은 각 클라이언트가 필요한 데이터 형태를 쿼리로 직접 지정하게 한다.

3. 절차나 명령으로 표현하는 편이 자연스러운 작업 → RPC over HTTP. 일부 작업은 리소스에 자연스럽게 대응하지 않는다. 코드 컴파일, 비디오 변환, 프롬프트 완성은 명사형 리소스보다 동사형 작업에 가깝다. 풍부한 페이로드를 받는 POST 동작 엔드포인트인 OpenAI의 /v1/chat/completions도 RPC의 성격을 띤다. 이를 억지로 REST로 설명할 필요는 없다.

gRPC — 타입 계약, 이진 형식, HTTP/2

gRPC에서는 서비스를 Protobuf의 .proto 파일로 정의한다. 도구가 이 정의를 바탕으로 다양한 언어의 타입 안전 클라이언트와 서버 코드를 생성하며, 전송에는 HTTP/2 위의 이진 Protobuf 형식을 사용한다. 주요 장점은 다음과 같다.

  • 명확한 타입 계약. 필드가 문자열인지 숫자인지 추측할 필요가 없으며 .proto 파일이 계약을 정의한다.
  • 자동 생성되는 코드. Go, Python, Java, TypeScript 클라이언트가 같은 .proto에서 생성되고 동일한 타입 검사를 적용받는다.
  • HTTP/2 다중화. 하나의 연결에서 여러 호출을 동시에 처리할 수 있다.
  • 양방향 스트리밍. 양쪽에서 스트림을 전송할 수 있으며 WebSocket 프레이밍을 직접 설계하는 것보다 단순하다.

비용도 있다. 이진 형식은 브라우저 개발자 도구에서 바로 읽기 어렵고, CDN 지원이 제한적이어서 gRPC-Web 같은 별도 계층이 필요할 수 있다. .proto 스키마를 발전시키는 과정에도 규율이 필요하다. 내부 서비스 메시에서는 이러한 비용을 감수할 가치가 있지만, 소비자가 다양한 공개 API에는 적합하지 않을 수 있다.

GraphQL — 클라이언트가 선택하는 응답 형태

GraphQL은 클라이언트가 필요한 필드를 정확히 서술한 쿼리를 보내는 단일 엔드포인트를 노출한다. 보통 POST /graphql을 사용한다.

{
  user(id: "u_42") {
    name
    email
    orders(limit: 3) {
      id
      total
      items { name }
    }
  }
}

서버는 각 필드를 독립적으로 해석하고, 클라이언트는 요청한 데이터만 받는다. 주요 장점은 다음과 같다.

  • 과다 조회와 부족 조회를 줄인다. 같은 엔드포인트에서 모바일은 작은 페이로드를, 웹 관리자 화면은 더 풍부한 페이로드를 요청할 수 있다.
  • 관련 데이터를 한 번의 왕복으로 가져온다. 클라이언트에서 여러 REST 요청을 연쇄적으로 보내는 문제를 줄일 수 있다.
  • 강한 타입 계약을 제공한다. GraphQL 스키마가 필드와 관계의 타입을 정의한다.

GraphQL 역시 비용이 작지 않다. 요청마다 쿼리 형태가 달라 캐시하기 어렵고, resolver를 주의 깊게 구현하지 않으면 복잡한 쿼리 하나가 DB에 과도한 부하를 줄 수 있다. 호출마다 요청 형태가 달라지므로 CDN이나 proxy 같은 중간 장치가 최적화할 여지도 줄어든다.

RPC — 작업에 자연스러운 명사가 없을 때

절차 중심의 작업에는 JSON 페이로드를 받는 동작 엔드포인트에 POST를 사용하는 RPC over HTTP가 더 정직한 표현일 수 있다. 예를 들면 POST /complete, POST /transcode, POST /sign이 있다. OpenAI Chat Completions API도 이와 같은 형태다. 이를 억지로 REST라고 부를 이유는 없다.

현대적인 RPC 패턴에는 TypeScript 전 구간에 타입을 연결하는 tRPC, JSON-RPC 2.0, 일반적인 POST with JSON 방식이 있다. 적합한 지점에 RPC를 선택하는 것은 REST를 포기한다는 의미가 아니다. 프로토콜을 트래픽 형태에 맞추는 설계 판단이다.

아키텍처 양식은 선택 기준이지 우열을 가르는 미덕이 아니다. REST가 많은 API 트래픽에 잘 맞는 이유는 캐시, 중간 장치, 브라우저와 호환되고 무상태 요청을 재시도하기 쉽기 때문이다. 나머지 통신 형태에는 더 적합한 도구가 있을 수 있다. 트래픽의 특성을 살펴보고 선택 이유를 명확히 설명할 수 있어야 한다.

선택을 위한 기준

  • 양쪽의 타입 계약이 중요한 내부 서비스 메시 → gRPC.
  • 서로 다른 필드 조합을 요구하는 다양한 클라이언트 → GraphQL.
  • 명사형 리소스로 표현하기 어려운 작업 중심 트래픽 → RPC over HTTP.
  • 공개 CRUD, 외부 통합, 브라우저 앱 → REST.
  • 실시간 양방향 통신 → WebSocket 또는 내부 시스템의 gRPC 스트리밍.
  • 서버에서 클라이언트로 보내는 단방향 이벤트 → SSE.

대부분의 운영 시스템은 이 가운데 두세 가지를 함께 사용한다. cwkPippa는 REST + SSE + WebSocket을 조합한다. 일반적인 SaaS는 공개 API에 REST, 풍부한 웹 앱에 GraphQL, 내부 통신에 gRPC를 사용할 수 있다. 여러 방식을 조합하는 것이 자연스러우며, 하나의 방식만 사용하는 시스템이 오히려 드물다.

cwkPippa의 선택 다시 보기

cwkPippa는 REST + SSE + WebSocket을 사용한다. 단일 서비스이므로 별도의 내부 서비스 메시와 gRPC 타입 계약을 도입할 필요가 없다. 웹 클라이언트도 하나이며 REST 엔드포인트가 필요한 조회 형태를 충분히 제공하므로 GraphQL을 사용하지 않는다. 리소스보다 작업에 가까운 경우에는 RPC 방식의 동작 엔드포인트(POST /api/council/{id}/finalize)를 둔다. 하나의 방식으로 모든 문제를 해결하려 하지 않고 요구에 맞는 방식을 의도적으로 조합한 설계다.

Code

gRPC: 타입 계약이 있는 서비스 정의와 다중 언어 코드 생성·protobuf
// gRPC service 정의 — users.proto
syntax = 'proto3';

package users.v1;

service UsersService {
  rpc ReadUser    (ReadUserRequest)   returns (User);
  rpc CreateUser  (CreateUserRequest) returns (User);
  rpc ListUsers   (ListUsersRequest)  returns (stream User);  // server streaming
  rpc Chat        (stream ChatMsg)    returns (stream ChatMsg);  // 양방향 streaming
}

message User {
  string id    = 1;
  string name  = 2;
  string email = 3;
}

message ReadUserRequest  { string id = 1; }
message CreateUserRequest { string name = 1; string email = 2; }
message ListUsersRequest { int32 limit = 1; }
message ChatMsg { string text = 1; }

// 어느 언어든 client 생성:
//   protoc --python_out=. --grpc_python_out=. users.proto
//   protoc --go_out=. --go-grpc_out=. users.proto
//   protoc --ts_out=. users.proto
// 같은 계약, 모든 언어; HTTP/2 multiplexed; bidi streaming 내장.
GraphQL: 타입 스키마와 클라이언트가 선택하는 쿼리 형태·graphql
# GraphQL query — client 가 shape 선택
query GetUserWithOrders($userId: ID!) {
  user(id: $userId) {
    name
    email
    orders(limit: 3, status: ACTIVE) {
      id
      total
      items {
        name
        price
      }
    }
  }
}

# Server 가 각 필드 resolve; client 가 요청한 거 정확히 받음.
# Mobile 이 더 작은 query 보냄; web admin 이 더 풍부한 거. 같은 endpoint.

# Schema (server-defined)
type User {
  id: ID!
  name: String!
  email: String
  orders(limit: Int = 10, status: OrderStatus): [Order!]!
}

type Order {
  id: ID!
  total: Float!
  items: [Item!]!
}
RPC over HTTP: 풍부한 JSON 페이로드를 받는 동작 중심 URL·python
# RPC-over-HTTP — rich JSON 가진 action 에 POST
# 이게 정직히 OpenAI 의 Chat Completions API
import httpx

resp = httpx.post(
    'https://api.openai.com/v1/chat/completions',
    headers={'Authorization': 'Bearer sk-...'},
    json={
        'model': 'gpt-4o',
        'messages': [{'role': 'user', 'content': 'Hi'}],
        'temperature': 0.7,
        'stream': True,
    },
)
# /v1/chat/completions 가 OPERATION (이 prompt 완료), resource 아님.
# URL 이 verb-named; body 가 procedure call. RPC-over-HTTP.
# 정직한 framing: 'API.' 부정직한 framing: 'REST API.'

External links

Exercise

실제 또는 가상의 기능이나 서비스를 하나 선택하고 세 가지 설계를 작성한다. (1) REST로 설계하는 방법, (2) GraphQL로 설계하는 방법, (3) gRPC 또는 RPC로 설계하는 방법을 비교한다. 각 방식마다 구체적인 장점 하나와 비용 하나를 적는다. 마지막으로 한 방식을 선택하고, 해당 트래픽 형태에 그 방식이 다른 방식보다 더 적합한 이유를 두 문장으로 설명한다. 보너스 과제로 Envoy, Kubernetes, Etcd 등이 공개한 gRPC .proto 파일을 읽고, 타입 계약이 직접 작성한 OpenAPI 명세와 어떻게 다르게 느껴지는지 정리한다.
Hint
REST는 캐시 가능성, 브라우저 호환성, 중간 장치 지원에 강하다. GraphQL은 클라이언트 유연성과 over-fetching 방지에 강하다. gRPC는 타입 계약, HTTP/2 다중화, 스트리밍에 강하다. 반대로 클라이언트 요구가 크게 다르면 REST 엔드포인트가 지나치게 늘 수 있고, GraphQL은 캐시가 어려우며, gRPC는 브라우저 도구로 직접 살펴보기 어렵다. 선택 근거를 두 문장으로 설명하는 연습 자체가 중요한 설계 규율이다.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 피파warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.