🔷 GraphQL8. 이론 & 대안05 — GraphQL vs RPC

05 — GraphQL vs RPC

한 줄 답: JSON-RPC, XML-RPC, OpenRPC는 모두 함수 호출 패러다임 — 클라이언트가 서버 함수의 이름과 인자를 보내면 결과가 온다. GraphQL은 그래프 쿼리 패러다임 — 클라이언트가 원하는 결과 모양을 보내면 엔진이 그래프를 traversal해서 결과를 만든다. 같은 “Remote” + “Procedure” 글자를 써도 모델이 다르다.


Why — 왜 “RPC와 GraphQL”이 자주 혼동되나

/graphql endpoint에 POST를 보낸다는 점에서 겉모양은 JSON-RPC와 닮았다. 그래서 종종 “GraphQL은 그냥 JSON-RPC의 변형 아닌가”라는 오해가 나온다. 또는 gRPC가 “RPC”라서 gRPC와 GraphQL의 비교(03)도 RPC 일반과의 비교인 줄로 착각한다.

이 챕터는 그 혼동을 정리한다 — RPC family(JSON-RPC, XML-RPC, OpenRPC, gRPC, SOAP)와 GraphQL의 패러다임 차이.

RPC 계열 (function call)GraphQL (graph query)
클라이언트가 함수 이름인자를 보냄클라이언트가 결과의 모양을 보냄
응답 type은 서버 함수의 return type응답 type은 쿼리가 정의한 type
addOrder(item, qty) 처럼 동사 중심{ order { items { qty } } } 처럼 명사 중심
그래프 traversal 없음그래프 traversal 기본
한 호출 = 한 함수 = 평면 응답한 쿼리 = 한 트리 = 중첩 응답

How — 각 RPC 변종이 어떻게 다른가

1) RPC family 한눈에

  • XML-RPC (1998): 함수 이름 + 인자를 XML로 직렬화. 단순. 거의 사라짐.
  • JSON-RPC 2.0 (2010): XML 대신 JSON. 가벼움. 블록체인 (Ethereum) 등에서 여전히 사용.
  • SOAP: 무겁고 복잡한 XML + WSDL 스키마. 엔터프라이즈에서 잔존.
  • gRPC: HTTP/2 + Protobuf 위의 RPC. 강타입, 빠름. (03 참고)
  • OpenRPC: JSON-RPC의 OpenAPI — 함수 시그니처 메타 명세.

2) JSON-RPC vs GraphQL — wire 비교

JSON-RPC 2.0 요청:

{
  "jsonrpc": "2.0",
  "method": "user.getById",
  "params": { "id": 1 },
  "id": 42
}

JSON-RPC 2.0 응답:

{
  "jsonrpc": "2.0",
  "result": {
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com",
    "createdAt": "2024-01-01"
  },
  "id": 42
}

method + params를 보내고, 함수의 전체 return을 받는다. 모양 결정은 서버.

GraphQL 요청:

{
  "query": "{ user(id: 1) { name } }"
}

GraphQL 응답:

{
  "data": { "user": { "name": "Alice" } }
}

결과의 모양을 보내고, 그 모양 그대로 받는다. 모양 결정은 클라이언트.

겉으로는 비슷한 JSON POST지만, 의미 모델이 정반대다.

3) 그래프 traversal의 유무

// JSON-RPC — N개 호출 필요
const user = await call("user.getById", { id: 1 });
const posts = await call("post.byUserId", { userId: 1 });
const author = await Promise.all(
  posts.map(p => call("user.getById", { id: p.authorId }))
);
// → 1 + 1 + N round trips
# GraphQL — 한 호출
{
  user(id: 1) {
    posts {
      title
      author { name }
    }
  }
}
# → 1 round trip

RPC는 함수 단위다. 관계를 따라가려면 클라이언트가 여러 호출을 엮어야. GraphQL은 관계를 따라가는 traversal이 쿼리 자체에 내장.

→ 이 차이가 GraphQL의 가장 큰 가치 제안이고, RPC가 줄 수 없는 것이다.

4) Batching — RPC의 회피책

JSON-RPC 2.0은 batch request를 지원한다.

[
  { "jsonrpc": "2.0", "method": "user.getById", "params": { "id": 1 }, "id": 1 },
  { "jsonrpc": "2.0", "method": "post.byUserId", "params": { "userId": 1 }, "id": 2 }
]

→ N round trip은 1로 줄어들지만, 클라이언트가 여전히 모든 호출을 명시해야 함. 그리고 결과의 join도 클라이언트 책임. GraphQL의 관계 자동 traversal이 아니다.

5) OpenRPC — RPC family의 GraphQL Schema 시도

OpenRPC는 JSON-RPC에 메타 명세를 추가한다 — 함수 시그니처, 인자 type, return type을 적은 JSON 스펙. OpenAPI가 REST에 한 일을 JSON-RPC에 한다.

{
  "openrpc": "1.2.6",
  "methods": [
    {
      "name": "user.getById",
      "params": [{ "name": "id", "schema": { "type": "integer" } }],
      "result": { "schema": { "$ref": "#/components/schemas/User" } }
    }
  ]
}

type 안전자동 codegen은 가능해진다. 하지만 그래프 traversal은 여전히 없다. RPC family의 한계.


What — 패러다임 차이 요약

동사 중심 vs 명사 중심

RPC (동사 중심):

createUser(...)
getUser(...)
updateUser(...)
deleteUser(...)
getUserPosts(...)
getPostComments(...)
addComment(...)
...

함수가 무한히 늘어남. 화면이 늘면 함수도 늘어남.

GraphQL (명사 중심):

type User { posts: [Post] }
type Post { comments: [Comment] }
type Comment { author: User }
type Mutation {
  createUser(...)
  addComment(...)
}

명사 그래프를 한 번 정의하고, 쿼리는 그 그래프를 다양하게 traversal. 새 화면이 와도 스키마 변경 없음.

함수의 비교

API함수/쿼리 가능한 수
잘 짠 REST리소스 수 × HTTP 메서드 수
잘 짠 JSON-RPC함수 수
잘 짠 GraphQL조합 폭발 — 같은 스키마로 수십억 가지 쿼리 가능

GraphQL은 스키마라는 조합 base가 있어서, 클라이언트가 명시적 함수를 정의하지 않은 쿼리도 받을 수 있다. RPC는 모든 호출이 미리 정의된 함수여야 한다.

Versioning 모델

도구versioning
XML-RPC함수 이름에 v2 (getUser_v2)
JSON-RPC함수 이름에 v2
OpenRPCAPI URL에 v2 (/v2/rpc)
gRPCproto package 버전
GraphQL버전 없음 — 필드 deprecate, 새 필드 추가

GraphQL의 no versioning명사 중심의 결과다 — 새 데이터 모양이 필요해도 그래프에 노드 추가면 됨, 새 함수 만들 필요 없음.


What-if — 잘못 선택하면

1) JSON-RPC로 모바일 News Feed 구현

→ 화면 한 장에 수십 호출 + 클라이언트 join — 모바일에서 수 초 대기. 대응: GraphQL의 그래프 traversal.

2) GraphQL로 단순 함수형 API (/sendEmail, /charge 등)

→ 스키마 overhead + codegen — 그냥 함수 하나면 되는 자리. 대응: JSON-RPC or REST.

3) “RPC와 GraphQL은 같다”고 보고 함수 묶음처럼 GraphQL 설계

type Mutation { getUser, getPost, getComment, ... }로 모든 query를 mutation으로 짜는 안티패턴. 대응: query는 명사 그래프, mutation은 동사 효과 분리.

4) OpenRPC vs GraphQL을 비교 시 문법 수준에서만 봄

→ “둘 다 JSON으로 method를 적는다”고 결론. 대응: 그래프 traversal 유무가 핵심 차이.

5) GraphQL을 batched JSON-RPC로만 봄

→ subscription, fragment, federation, codegen 같은 language-level 도구를 전혀 못 씀. 대응: GraphQL은 language다 — RPC family와 다른 가족.


Insight — 흥미로운 이야기

”Ethereum이 JSON-RPC를 고른 이유”

Ethereum 노드는 JSON-RPC를 표준 인터페이스로 쓴다 (eth_getBlockByNumber, eth_call 등). GraphQL은 EIP-1767대안 인터페이스로만 존재. 이유 — 블록체인 호출은 함수 호출이 본질 이다. 트랜잭션 보내기, 잔고 조회 — 동사 중심. 그래프 traversal이 필요 없는 영역. RPC가 맞는 자리의 전형.

”SOAP의 무덤”

1999년 SOAP은 *“웹 서비스의 미래”*로 추앙받았다. WSDL이라는 강타입 schema, namespace, security 표준 — 모든 게 있었다. 그러나 2010년대 너무 무겁다는 이유로 거의 죽었다. JSON-RPC와 REST가 자리를 차지하고, GraphQL이 마지막을 채웠다. schema가 있다고 살아남는 게 아니다 — 적합성이 맞아야 산다. SOAP의 교훈을 GraphQL도 새겨야 한다 — 잘못된 자리에 쓰면 무거움이 죽음이 된다.

”JSON-RPC의 끈질긴 생존”

JSON-RPC는 15년이 지나도 살아 있다 — Ethereum, Solana, Bitcoin Core, VS Code Language Server Protocol(LSP) 등. 이들의 공통점 — 함수 호출이 본질이고, 그래프 traversal이 필요 없는 자리. LSP는 “텍스트 에디터가 언어 서버에 goto_definition 호출” 같은 동사 중심 통신.

각 도구는 자기 자리가 있다. GraphQL이 모든 자리를 차지하지 못하는 이유.


요약 + 다이어그램

RPC family와 GraphQL은 패러다임이 다르다. RPC는 함수 호출 — 동사 중심, 평면 응답, 호출이 늘면 함수도 늘어남. GraphQL은 그래프 쿼리 — 명사 중심, 중첩 응답, 한 스키마로 수많은 쿼리 표현. 함수 호출이 본질인 자리(블록체인·LSP·결제)에서는 RPC가 맞고, 데이터 그래프가 본질인 자리에서는 GraphQL이 맞다.

다음 문서: 06-bff-pattern.mdx — 그래서 GraphQL이 가장 자연스러운 자리가 BFF다. 왜 그런가?