John James 블로그 포스트 번역
2025년 12월 14일 게시 | 읽는 시간 약 3분
나는 Apollo Client와 Server를 사용하여 실제 엔터프라이즈급 애플리케이션에서 GraphQL을 몇 년간 사용해왔다.
장난감 앱이 아니다. 초기 단계의 스타트업도 아니다. 여러 팀, BFF, 다운스트림 서비스, 관찰성 요구사항, 그리고 실제 사용자가 있는 제대로 된 프로덕션 환경이다.
그 모든 시간을 거친 후, 나는 꽤 평범한 결론에 도달했다:
GraphQL은 실제 문제를 해결하지만, 그 문제는 사람들이 인정하는 것보다 훨씬 더 틈새 시장이다. 대부분의 엔터프라이즈 환경에서는 이미 다른 곳에서 해결되었고, 트레이드오프를 모두 고려하면 GraphQL은 종종 순손실이 된다.
이것은 "GraphQL은 나쁘다"는 글이 아니다. "허니문이 끝난 후의 GraphQL"에 관한 글이다.
GraphQL이 해결하려는 것
GraphQL이 해결하려는 주요 문제는 오버페칭(overfetching)이다.
아이디어는 간단하고 매력적이다:
- 새로운 UI 요구사항이 생길 때마다 백엔드를 변경할 필요가 없다
종이 위에서는 훌륭하다. 실제로는 훨씬 복잡하다.
오버페칭은 이미 BFF로 해결되었다
대부분의 엔터프라이즈 프론트엔드 아키텍처는 이미 BFF(Backend for Frontend)를 가지고 있다.
그 BFF는 구체적으로 다음을 위해 존재한다:
BFF 뒤에서 REST를 사용하고 있다면, 오버페칭은 이미 해결 가능하다. BFF는 응답 범위를 좁혀서 UI가 신경 쓰는 것만 반환할 수 있다.
맞다, GraphQL도 이것을 할 수 있다. 하지만 사람들이 간과하는 부분이 있다.
대부분의 다운스트림 서비스는 여전히 REST다.
그래서 이제 GraphQL 레이어는 여전히 다운스트림 REST API에서 오버페칭을 해야 하고, 응답을 다시 형성해야 한다. 오버페칭을 제거한 것이 아니다. 단지 한 계층 아래로 옮긴 것일 뿐이다.
이것만으로도 GraphQL의 주요 판매 포인트를 크게 약화시킨다.
GraphQL이 여기서 이기는 경우가 있다. 여러 페이지가 같은 엔드포인트를 호출하지만 약간 다른 필드가 필요한 경우, GraphQL은 쿼리별로 그 차이를 범위 지정할 수 있다.
하지만 거래에 대해 솔직해지자.
보통 요청당 몇 개의 필드를 절약하는 것에 대해 이야기하고 있는데, 그 대신:
이것은 몇 킬로바이트를 절약하기 위한 매우 비싼 거래다.
구현 시간이 REST보다 훨씬 길다
GraphQL은 REST BFF보다 구현하는 데 훨씬 더 오래 걸린다.
REST를 사용하면 일반적으로:
GraphQL을 사용하면 이제 다음을 해야 한다:
- 스키마, 리졸버, 클라이언트를 동기화 상태로 유지한다
GraphQL은 소비 최적화를 생산 속도의 대가로 한다.
엔터프라이즈 환경에서는 생산 속도가 이론적 우아함보다 더 중요하다.
관찰성이 기본적으로 더 나쁘다
이것은 충분히 논의되지 않는 부분이다.
GraphQL은 이상한 상태 코드 규칙을 가지고 있다:
- 실행 중에 뭔가 실패하면
errors 배열과 함께 200
관찰성 관점에서 이것은 고통스럽다.
REST를 사용하면:
대시보드를 2XX로 필터링하면 그 요청들이 성공했다는 것을 안다.
GraphQL을 사용하면 200은 여전히 부분 또는 완전한 실패를 의미할 수 있다.
맞다, Apollo는 이 동작을 커스터마이즈할 수 있게 해준다. 하지만 그것이 바로 요점이다. 당신은 REST가 기본으로 제공하는 것으로 돌아가기 위해 추가 설정, 추가 규칙, 추가 정신적 오버헤드에서 계속 세금을 낸다.
이것은 당신이 온콜 상태일 때 중요하지, 블로그 포스트를 읽을 때는 아니다.
캐싱은 놀라워 보이지만 실제로 사용하면 다르다
Apollo의 정규화된 캐싱은 정말 인상적이다.
이론상으로는. 실제로는 취약하다.
단 하나의 필드만 다른 두 개의 쿼리가 있으면, Apollo는 그것들을 별개의 쿼리로 취급한다. 그러면 다음과 같이 수동으로 연결해야 한다:
그 시점에서:
한편, REST는 행복하게 몇 개의 추가 필드를 오버페칭하고, 전체 응답을 캐시하고, 계속 진행한다. 추가 킬로바이트는 싸다. 복잡성은 아니다.
ID 요구사항은 누수되는 추상화다
Apollo는 기본적으로 모든 객체가 id 또는 _id 필드를 가지기를 기대하거나, 커스텀 식별자를 설정해야 한다.
그 가정은 많은 엔터프라이즈 API에서 성립하지 않는다.
많은 API들:
- 전역적으로 식별 가능한 엔티티로 모델링되지 않는다
그래서 이제 BFF는 GraphQL 클라이언트를 만족시키기 위해 로컬에서 ID를 생성해야 한다.
그것은 다음을 의미한다:
오버페칭을 줄이는 것이 원래 목표였다는 점을 고려하면 아이러니하다.
REST 클라이언트는 이런 종류의 제약을 부과하지 않는다.
파일 업로드와 다운로드는 어색하다
GraphQL은 단순히 바이너리 데이터에 적합하지 않다.
실제로는 다음과 같이 끝난다:
- 그 다음 REST를 사용해서 파일을 어쨌든 가져온다
PDF 같은 큰 페이로드를 GraphQL 응답에 직접 포함시키면 응답이 부풀어지고 성능이 악화된다.
이것만으로도 "단일 API" 이야기를 깨뜨린다.
온보딩이 더 느리다
대부분의 프론트엔드 및 풀스택 개발자는 GraphQL보다 REST에 훨씬 더 경험이 많다.
GraphQL을 도입하는 것은 다음을 의미한다:
그 학습 곡선은 마찰을 만들고, 특히 팀이 빠르게 움직여야 할 때 그렇다.
REST는 지루하지만, 지루함은 매우 잘 확장된다.
오류 처리가 필요한 것보다 더 어렵다
GraphQL 오류 응답은... 이상하다.
당신은 다음을 가지고 있다:
- nullable vs non-nullable 필드
- 어느 리졸버가 실패했고 왜 실패했는지 추적할 필요
이 모든 것이 간접성을 추가한다.
간단한 REST 설정과 비교해보자:
간단한 오류가 우아한 오류보다 이해하기 쉽다.
최종 결과
GraphQL은 절대적으로 유효한 사용 사례가 있다.
하지만 대부분의 엔터프라이즈 환경에서:
모든 것을 합산하면, GraphQL은 종종 좁은 문제를 해결하면서 더 광범위한 새로운 문제들을 도입하게 된다.
그래서 프로덕션에서 몇 년간 사용한 후, 나는 이렇게 말하고 싶다:
GraphQL은 나쁘지 않다. 단지 틈새 시장일 뿐이다. 그리고 당신은 아마 그것이 필요하지 않을 것이다.
특히 당신의 아키텍처가 이미 그것이 설계된 문제를 해결했다면 말이다.