🔷 GraphQL8. 이론 & 대안02 — GraphQL vs REST

02 — GraphQL vs REST

한 줄 답: *“REST vs GraphQL”*은 잘못된 프레임이다. REST는 HTTP 리소스 스타일이고 GraphQL은 query language다 — 다른 축의 도구다. 한 시스템에 둘 다 공존하는 것이 정상이며, 적합 자리는 전혀 다르다.


Why — 왜 이 비교가 그렇게 흔히 잘못되나

GraphQL 등장 직후 거의 모든 글이 “REST is dead, long live GraphQL”이라고 외쳤다. 10년이 지난 지금 결론은 정반대다 — GitHub는 REST와 GraphQL을 둘 다 유지하고, Stripe는 여전히 REST만 쓰며, Shopify는 GraphQL을 권장하지만 REST를 지원한다. 이 패턴은 우연이 아니다.

흔한 잘못된 프레임진실
”GraphQL은 REST의 진화형”REST는 style, GraphQL은 language — 같은 축이 아니다
”REST는 over-fetching이 문제니까 GraphQL이 이긴다”over-fetching이 비싼 곳에서만 이긴다. 모바일이 아니면 문제가 아니다
”REST는 N개 endpoint니까 GraphQL이 이긴다”endpoint 수가 복잡도는 아니다. 잘 설계된 REST는 URL이 곧 문서
”GraphQL이 모든 면에서 낫다”HTTP 캐시·CDN·browser dev tools·file upload — REST가 압승하는 영역이 많다

이 비교의 정답은 **“무엇을 하는 자리인가”**를 먼저 묻는 것이다.


How — 두 도구의 적합 자리가 어떻게 갈리나

1) 비교 매트릭스

차원RESTGraphQL
본질HTTP 리소스 모델 (Fielding 박사 논문)클라이언트 주도 query language
계약OpenAPI (권장)SDL (의무)
endpoint리소스마다 하나/graphql 하나
응답 모양서버 결정 (고정)요청 결정 (가변)
HTTP 캐시최고Cache-Control·ETag·CDN어려움 — POST + 매번 다른 body
versioningURL/header (/v2/)없음 — schema 추가/Deprecate로 진화
file uploadmultipart/form-data 표준multipart spec 별도 합의 필요
error modelHTTP statuserrors 배열 (HTTP는 200)
introspectionOpenAPI 별도 생성사양 의무
streamingSSE·chunked·rangesubscription·@defer
공용 API에 노출최적 — curl 한 줄로 시작learning curve 있음

2) REST가 잘 맞는 자리

  • public API: Stripe, AWS, GitHub Public API — 외부 개발자가 curl 한 줄로 시작할 수 있는 것이 가장 큰 자산. OpenAPI 한 장이면 SDK가 자동 생성된다.
  • CDN/edge cache: GET /products/123Cache-Control: max-age=300 한 줄로 CDN이 캐시한다. GraphQL은 모든 게 POST + 매번 다른 body라 CDN이 무력.
  • 큰 파일: S3 presigned URL, range request, multipart upload — REST의 고유 강점. GraphQL로 50MB 영상을 받으려 하지 마라.
  • 단순 CRUD: GET/POST/PUT/DELETE /users/:id — 이 모양이 그대로 의미다. GraphQL이 더 적합할 이유가 없다.

3) GraphQL이 잘 맞는 자리

  • 가변 모양 클라이언트: 모바일에선 작은 모양, 웹에선 풍부한 모양, TV에선 또 다른 모양. REST로 다 만족시키려면 endpoint가 폭발하거나 over-fetching이 폭발한다.
  • BFF: 06-bff-pattern.mdx에서 자세히 — GraphQL의 가장 자연스러운 자리.
  • 여러 서비스 aggregate: subgraph federation으로 여러 백엔드를 한 그래프로 합친다. REST로는 client-side join이 필요.

4) 둘이 함께 사는 패턴

가장 흔한 production 구성은 공존이다.

  • 외부: REST (api.example.com/v1/...) — public, cacheable, OpenAPI
  • 내부 클라이언트: GraphQL BFF (api.example.com/graphql) — flexible, type-safe
  • 두 layer는 공존하고 공유 데이터 모델을 가진다

이게 GitHub, Shopify, Yelp, Netflix 등의 실제 구성이다.


What — 구체적 비교 시나리오

시나리오 A — News Feed (모바일 앱 첫 화면)

REST 접근:

GET /v1/me                     → 사용자 프로필
GET /v1/me/feed?limit=20       → 피드 목록
GET /v1/posts/:id/comments     → 각 포스트의 코멘트 (×20)
GET /v1/users/:id              → 각 작성자 프로필 (×20)

→ 한 화면을 위해 수십~수백 round trip. 셀룰러 네트워크에서 수 초 대기.

GraphQL 접근:

query Feed {
  me {
    name
    feed(first: 20) {
      title
      author { name avatar }
      comments(first: 3) { body }
    }
  }
}

→ 한 번의 요청, 한 번의 응답. 정확히 원하는 모양만.

판정: 이런 화면에서는 GraphQL이 압도적이다. Facebook이 2012년 만든 이유.

시나리오 B — 결제 처리 (서버-서버)

REST 접근:

POST /v1/charges
Content-Type: application/json
 
{ "amount": 1000, "currency": "krw", "source": "..." }
 
201 Created
   Location: /v1/charges/ch_abc

GraphQL 접근:

mutation { createCharge(input: { amount: 1000, ... }) { id status } }

판정: REST가 더 적합. 이유 — (a) 외부 개발자가 즐겨 쓰는 곳, (b) idempotency key 헤더가 자연, (c) 결제는 단일 리소스 액션이지 그래프 조회가 아님, (d) HTTP status code가 결제 결과를 잘 표현. Stripe가 GraphQL을 안 만든 이유.

시나리오 C — 이미지 다운로드

REST: GET /images/abc.jpg — CDN이 그대로 캐시, range request 지원. GraphQL: 시도하지 마라. binary는 GraphQL의 영역이 아니다.

판정: REST만. GraphQL은 메타데이터({ image(id) { url width height } })만 받고, 실제 binary는 url로 직접.

시나리오 D — 어드민 대시보드 (가변 위젯)

위젯마다 필요한 데이터가 다르다.

REST: 위젯이 늘 때마다 endpoint 추가, 또는 over-fetching 감수. GraphQL: 위젯이 자기에게 필요한 모양을 직접 선언, 추가 endpoint 없음.

판정: GraphQL이 적합. 내부 가변 UI의 전형적 자리.


What-if — 잘못 선택하면

1) public API에 GraphQL만 노출

→ 외부 개발자가 learning curve에 막혀 채택률 하락. → Stripe·Twilio가 REST만 유지하는 이유. 대응: GraphQL을 쓰더라도 REST facade를 함께 노출.

2) 내부 모바일 앱에 REST만

→ News Feed류 화면에서 수십 round trip. 모바일에서 체감 느림. 대응: BFF에 GraphQL을 두고 그 는 REST로 둘 수 있다.

3) 큰 파일을 GraphQL로 전송

→ JSON 안에 base64로 영상을 넣어 3배 부풀어 메모리 폭발. 대응: GraphQL은 metadata만, binary는 별도 URL.

4) “REST는 죽었다”고 믿고 전부 GraphQL로 이관

→ CDN·HTTP 캐시·webhook·file upload — 모두 다시 만들어야 함. 6개월 뒤 후회. 대응: 공존을 default로 본다.

5) “GraphQL은 그냥 batch REST”라고 생각하고 single-level 쿼리만

→ GraphQL의 그래프 traversal·codegen·BFF 가치를 전혀 못 씀. REST가 차라리 단순했을 것. 대응: 진짜로 그래프가 필요한 자리에만 GraphQL.


Insight — 흥미로운 이야기

”GitHub의 듀얼 API”

GitHub는 2017년 GraphQL API v4를 공개하면서 REST API v3를 유지한다고 선언했다. 이유는 단순했다 — 외부 개발자의 80%는 REST에 익숙했고, 내부 dashboard 팀의 80%는 GraphQL이 필요했다. 같은 회사 안에서 두 도구가 다른 청중을 받아낸다. 8년이 지난 지금도 두 API는 모두 운영 중이다.

”Roy Fielding의 침묵”

REST를 정의한 Roy Fielding은 GraphQL 등장 이후 거의 언급을 하지 않았다. 그의 논문(2000)이 정의한 REST는 style이지 protocol이 아니다 — “REST is dead”는 말 자체가 범주 오류라는 입장이다. style은 language로 대체될 수 없다.

”Apollo의 후회”

Apollo는 한때 “Replace REST with GraphQL” 슬로건을 강하게 밀었다. 2022년 이후 그들의 공식 문서는 톤이 바뀌었다 — “GraphQL은 데이터 access layer고, REST와 함께 살 수 있다”는 입장이다. 시장이 5년에 걸쳐 가르쳐 준 교훈.


요약 + 다이어그램

REST와 GraphQL은 다른 축의 도구다. REST는 HTTP 리소스 style, GraphQL은 query language. 한 시스템에 둘이 공존하는 것이 정상이며, 적합 자리는 전혀 다르다 — public/cacheable/file은 REST, 가변 모양 클라이언트/BFF는 GraphQL.

다음 문서: 03-graphql-vs-grpc.mdx — 그럼 서버-서버 통신에 쓰는 gRPC와는?