Web/GraphQL

GraphQL vs REST API

이미지 출처: nordicapis.com

최근 좋은 기회를 통해 GraphQL을 적극적으로 사용해볼 수 있는 경험이 생겼다. 이 과정에서 스스로 체감했던 REST API와의 차이점, 그로 인해 생기는 장단점, 그리고 REST API를 쓰면 좋을지 GraphQL을 쓰면 좋을지 고민한 내용을 적어보려고 한다. 만약 당신이 (얼마 전의 나처럼) GraphQL에 대한 간단한 정의만을 알고 있고 실제로 사용해본 적은 없으며 REST API만 사용해 봤다면 이 글에서 얻어갈 수 있는 게 많을 것이라고 생각한다.

 

 

GraphQL이 무엇인가?

GraphQL은 QL이라는 단어가 끝에 붙었듯이 Query Language다. 대부분 Query Language라 하면 SQL이 가장 먼저 떠오를 것 같은데, SQL이 DB에게 데이터를 질의하기 위해 사용하는 쿼리 언어라면 GraphQL은 웹 클라이언트가 웹 서버로부터 데이터를 요청하기 위해 사용하는 쿼리 언어다.

 

GraphQL은 (대부분의 상황에서) 웹 클라이언트 측에서 작성되고, 웹 서버는 이렇게 작성된 쿼리문을 클라이언트로부터 받게 된다. 서버는 이 쿼리문을 파싱해서 AST(Abstract Syntax Tree; 추상 구문 트리)라는 객체로 가공하고, 서버 내부 로직에 따라 처리하고 결과를 JSON 형태의 응답으로 클라이언트에게 돌려준다.

 

 

GraphQL과 REST API는 어떻게 다른가?

GraphQL과 REST API의 차이점에 대해 구글링을 해보면 가장 많이, 흔하게 나오는 문장은 "REST API는 엔드포인트가 여러 개인데, GraphQL은 엔드포인트가 하나다."라는 문장이다. 엔드포인트라는 것은 REST API 기준으로 클라이언트 측에서 서버로 데이터를 요청할 때 사용하는 주소(URL)와 메소드(GET, POST 등의 HTTP 메소드)의 조합을 의미한다. 즉, REST API에선 각각의 자원이 별도의 엔드포인트를 가지고 있다. 따라서 REST API에서는 특정 데이터를 요청하거나, 수정하거나 할 때 다른 엔드포인트를 사용해야 한다는 점이 특징이다.

 

이쯤 되면 GraphQL에서 엔드포인트가 하나라는 것은 무슨 의미인지 짐작이 갈 것이다. GraphQL에서는 하나의 루트 엔드포인트로 모든 요청을 보내게 된다. GraphQL에서는 각각의 자원을 구분할 때 엔드포인트를 기준으로 구분하지 않고, 클라이언트에서 작성된 쿼리문의 형식을 통해 구분한다. 이 점이 REST API와 GraphQL의 태생적 차이이며 이 차이로 인해 다른 모든 차이가 발생한다고 말해도 과언이 아니다.

 

그렇다면 GraphQL은 왜 이런 방식을 채택했는지 궁금해진다. 결론부터 말하자면 원하는 데이터를 최소한의 네트워크 요청을 통해 가져오게 하기 위해서다. 기존 REST API의 방식에서는 원하는 데이터를 가져오기 위해 여러 번의 데이터 요청을 하게 되는 경우가 종종 발생했다. 왜냐하면, 데이터의 구조를 서버에서 결정하기 때문이다. 클라이언트가 지금 당장 필요로 하지 않는 데이터까지 보낼 수 있다. 이런 경우에는 불필요한 데이터가 네트워크 상에서 전송되기 때문에 쓸데없는 낭비다(이 문제를 overfetching이라고 하며, 이 부분에 대한 자세한 이야기는 뒤에서 추가로 진행하도록 하겠다.). 게다가 훨씬 더 치명적인 점은 쓸데없는 데이터를 보내는 와중에 정작 필요한 데이터는 보내지 않을 수 있기 때문에 필요한 데이터를 받기 위해서는 여러 엔드포인트에 요청을 보내야 할 수도 있다는 점이다. (이 문제는 underfetching이라고 한다.) 이 문제 때문에 여러 번 요청을 보내야 하는데, 해당 API가 유료라고 생각하면.. 벌써부터 눈앞이 흐려진다.

 

GraphQL은 이와 다르게 요청할 데이터의 구조가 클라이언트에서 결정된다. 앞서 말했듯 클라이언트 측에서 작성된 쿼리문을 통해 통신하기 때문이다. 따라서 클라이언트가 매 상황마다 원하는 데이터만 쏙쏙 골라 담아서 서버에 요청할 수 있게 되고, 위에서 언급한 문제들을 이를 통해 한 방에 해결할 수 있다. 실제로 GraphQL을 통해서 개발을 해보니까 이 부분이 상당히 좋다고 느껴졌다.

 

 

아니 그러면 REST API는 왜 씀?

여기까지 읽어보면 REST API는 똥이고 GraphQL은 완벽한 신기술처럼 보인다. 그러나 현재에도 많은 곳에서 REST API를 사용하고 있는 걸 보면 이런 부분에 대해서 당연히 의문이 생긴다. 그래서 지금부터는 GraphQL을 쓰면서 내가 직접 체감했던 GraphQL의 숨겨진(?) 어두운 이면에 대해 말해보려고 한다. (사실 많은 한국어 문서에는 GraphQL의 단점에 대해서 정확히 설명하고 있지 않다.)

 

 

이래서 REST API를 쓰는구나!

1. GraphQL은 REST API보다 느리다.

 

숨겨진 GraphQL의 본모습

사실 GraphQL은 REST API보다 느리다. 엥? 아닌데요? GraphQL이 더 빠르다고 하는데요? <~ 이거 사실 거짓말이다. GraphQL은 이 포스트의 초반부에서도 설명했듯이 파싱 후 AST로 변환되는 과정을 거쳐야 하기 때문에 구조 상 REST API보다 절대 빠를 수가 없다. 그럼에도 불구하고 몇몇 한국어 문서에서는 GraphQL이 빠르다고 하는 내용이 있는데, 정확히 말하면 GraphQL이 underfetching과 overfetching 문제를 해결했기 때문에 통신 과정에서 자원의 크기가 최대의 효율로 전송되는 것이지 속도가 빠르다는 건 아니다. 따라서, 같은 양의 데이터 통신 과정에서는 REST API가 더 빠르다. 즉, 원하는 데이터를 REST API로도 한 번에 가져올 수 있으면 REST API를 쓰는 것이 더 낫다.

 

 

2. GraphQL은 파일 전송과 관련된 기능을 구현하기 너무 어렵다.

 

GraphQL로 파일 전송 기능을 구현 중인 개발자

GraphQL로 이미지 전송, 파일 전송 등의 기능을 구현하려면 너무 복잡하다. GraphQL은 JSON 형식의 요청만 이해할 수 있기 때문에 일반적인 파일 전송에서 사용되는 형식인 multipart/form-data 형식의 경우 별도로 미들웨어를 사용해서 파싱해야 한다. 그리고 GraphQL 공식 문서에서는 아직 파일 전송에서 사용할 수 있는 타입을 명확히 명시해두고 있지 않기 때문에 어쩔 수 없이 외부 라이브러리의 도움을 받아야 한다.

 

나는 이번 개발 과정에서 (Apollo Server 공식 문서에서 추천하고 있는) graphql-upload를 사용했는데, 이게 문제가 많았다. 결론은 최신 node.js 버전에서 정상적으로 호환이 되지 않는 이슈 때문이었는데, 아직 내가 버전 이슈로 인해 어려움을 겪은 경험이 많지는 않았어서 원인 파악 하는데만도 시간을 많이 소모했다. 웃긴 건 Apollo Server 버전마다, node.js 버전마다 graphql-upload 사용법이 전부 다르기 때문에 고려해야 할 사항도 많고 참조할만한 레퍼런스도 정상적으로 작동할 지 알 수가 없었다. 이 경험을 통해 외부 라이브러리에 대한 의존성이 높은 기능은 너무 개발하기 불편하다는 점을 뼈저리게 느끼게 되었다.

 

 

3. 다른 사람들 다 REST API 쓰는데 님 혼자 뭐함?

 

GraphQL을 만난 개발자

이 문제는 Google Cloud에서 제공하고 있는 API 관리 문서를 보고 고려하게 된 문제다. 이 문서를 보면 Don’t force GraphQL when REST makes more sense라는 문단을 두고, "REST가 더 적합한 것 같으면 GraphQL 쓰지 마라"라고 대놓고 엄포를 두고 있다. 이런 주장의 근거는 다음과 같다.

 

첫째, API를 만든 팀의 의도와 실제로 API를 사용하는 팀 간의 괴리가 발생할 수 있다. REST API는 유구한 전통을 자랑하고 있기 때문에 일관되고 안정적이며 직관적이라는 생각에서 출발한 결론이다.

 

둘째, overfetching, underfetching 문제가 그렇게 심각한 문제인가? 팀 내부에서 API 요청 2번 할 것을 1번으로 줄이는 게 통신 방법을 바꿔야 할 정도로 심각한 문제인가? 데이터의 양이 엄청나게 많지 않다면 심각한 수준이 아닐 것 같은데? 그리고 overfetching 또는 underfetching되는 데이터의 양이 너무 많으면 그냥 서버에서 API 설계를 수정하면 되는 것 아닐까?

 

셋째, GraphQL이 POST/PUT/DELETE하는 방식이 REST에 비해 너무 복잡하지 않은가? 실제로 GraphQL에서 데이터 변이를 발생시키는 mutation의 경우 필드 구조가 복잡해질수록 작성하기 너무 까다로워진다.

 

마지막으로, 앞에서 말한 모든 이유로 인해 생산성이 떨어질 우려가 있다. 서비스를 하고 있는 기업 입장에서는 GraphQL을 적극적으로 도입하기 힘든 가장 큰 이유가 이것이라고 생각한다. 결국 개발자가 겪는 어려움은 프로덕트의 생산성 문제와 직결된다. 실제로 이미 많은 퍼블릭 API들은 현재까지도 REST의 형식으로 API를 제공하고 있다. 왜 그럴까는 어쩌면 우리 모두가 이미 알고 있을 지도 모른다.

 

번외. GraphQL은 캐싱이 잘 되지 않는다?

이건 GraphQL의 단점에 대해 검색해보면 많은 한국어 문서에서 지적받는 GraphQL의 단점이길래 넣어봤다. 난 이 부분에 대해서 실제 개발하면서 전혀 공감하지 못했기 때문이다. 대부분의 GraphQL을 사용하는 환경에선 Apollo를 사용하고 있기 때문에 캐싱 문제에서는 자유롭다고 생각하기 때문이다. Apollo에서 제공하는 캐싱 정책은 대부분의 유즈 케이스를 핸들링할 수 있을 정도로 상당히 구체적이고 다양하다. 물론 GraphQL 자체에서 제공하는 캐싱 기능이 아니라는 점은 맞지만, 실제 개발 과정에서는 이게 단점이라고 여겨질 정도로 큰 문제는 아니라고 생각이 들었다. 난 아무튼 그랬음

 

 

그래서 뭘 쓰는 게 더 좋은가?

그래서 뭘 쓰는 게 좋은걸까? 이 질문에 대한 답을 내 나름대로 생각해봤는데.. 이 답은 어쩌면 이 긴 글 전체를 쓰레기통으로 보내버릴 수도 있을 것 같지만, 놀랍게도 내가 내린 정답은 '그냥 둘 다 쓰자'다.

 

좋은 건 스까 묵자

그냥 파일 전송하는 부분은 REST API 엔드포인트 하나 뚫어서 REST로 구현하고, 텍스트로 처리가 가능한 부분은 GraphQL로 구현하면 된다. 물론 내가 이번에 GraphQL을 사용하면서 백엔드와 프론트엔드 전부를 직접 개발했기 때문에 이런 결론이 나온 것 같기도 하다. 백엔드와 프론트엔드 개발자가 다른 경우 이렇게 서로 다른 API 구조를 섞어서 개발하면 사용하기가 더 까다로워 질 수도 있을 거라고 생각한다.

 

그럼에도 불구하고 사실 프론트엔드 입장에서 생각하면 GraphQL은 포기하기 싫을 정도로 매력적인 부분이 분명히 존재한다. Swagger 문서를 보면서 API 엔드포인트 일일히 뒤져가며 내가 뭘 써야되는지 찾을 바에 GraphQL Playground에서 자동 완성되는 스키마를 쓰면서 원하는 필드 쏙쏙 뽑아가며 개발하는 게 더 편하다.

 

그리고, GraphQL은 결국 JSON 형태로 객체를 반환해주기 때문에 프론트엔드 개발자가 별도의 변환을 진행하지 않고 바로 사용할 수 있다는 점도 너무 좋다. JSON stringify 이런 거 쓸 필요 없다. 심지어 TypeScript를 사용하고 있을 때, gql 스키마 기반으로 type을 자동 생성하도록 환경을 구성해두면 GraphQL 요청을 통해 받아올 데이터의 타입까지 예측해줘서 VSCode가 객체 속성을 자동으로 완성해주는데, 이 점은 정말 편했다.

 

개발자들도 편하게 개발하고 싶어하고, 그렇게 개발해야 능률과 생산성도 향상되니까 회사 입장에서도 이득이다. 이건 살짝 내가 봐도 억지 같지만 아무튼 이런 점에서 GraphQL이 가지고 있는 이런 장점은 충분히 중요하게 생각할 수 있을 만한 장점이라고 생각한다.

 

아무튼 이렇게 GraphQL의 기본 개념부터, REST API와 비교했을 때 각각의 장단점과 특징에 대해서 정리해 보고 내 나름대로 결론도 내 보았다.

 

결국 이 긴 글은 뻔하지만 절대적인 결론을 다시 한 번 마음 속에 새기게 해 주면서 끝나게 되었다.

 

역시 완벽한 기술은 없고, 상황에 딱 맞는 적절한 기술을 골라 쓰자!

 

 

References