예전에 회사에서 사이드 프로젝트로 새로운 레포를 만들 일이 있었습니다. 메인 프로덕트는 좀 보수적으로 스펙을 정하지만, 사이드 프로젝트인 만큼 조금 자유롭게 스펙을 정할 수 있었는데요, Frontend는 React로, Backend는 GraphQL / Apollo / Prisma를 이용했습니다. GrqphQL은 비교적 최신 기술이고, 기존의 REST와는 다른 방식을 사용하므로 가볍게 정리하고 넘어갈 필요성을 느꼈습니다. 이 중에 GraphQL에 관해서 조금만 자세히 알아보겠습니다.
GraphQL?
GraphQL은 간단히 말해 REST를 대체하는 API 표준입니다. 이것의 핵심은 클라이언트가 원하는 데이터를 정확하게 특정하여 요청하는 선언적인 데이터 불러오기
를 가능케 한다는 것인데요, Static한 데이터 구조를 반환하는 Endpoint 여러 곳을 두는 것이 아니라, 하나의 Endpoint를 노출시키고, 요청받은 Data만을 반환합니다.
Facebook에서 이것을 개발하게 된 이유는, 다음 세 가지로 압축할 수 있을 듯합니다.
- 모바일 사용, 저전력 장치, 느린 네트워크 환경의 증가로 효율적인 데이터 로딩이 필요해졌다.
- 네트워크를 통해 전송하는 Data량을 최소화할 필요가 있습니다.
- 다양한 Front End 프레임워크, 플랫폼이 등장했다.
- 각각의 요구 사항을 모두 충족시키는 하나의 API가 필요해졌습니다.
- 애자일 환경에서 빠른 기능 개발을 기대하게 되었다.
- REST에서는 종종 설계 변경에 대응하기 위해 복잡한 수정이 필요한 경우가 생기고는 했습니다.
REST와의 차이?
만일 어느 게시판
을 운영한다고 가정할 때, 특정 사용자
가 작성한 게시글의 제목
을 표시하고, 같은 화면에서 해당 사용자를 최근에 팔로잉한 사용자의 이름
을 표시해야 한다고 가정해보겠습니다. 일반적인 REST API의 경우라면 다음과 같은 절차가 필요하게 될 겁니다.
- 특정 사용자의 데이터를
/users/<id>
를 통해서 불러옵니다. - 해당 사용자의 모든 게시글을
/users/<id>/posts
를 통해서 불러옵니다. - 해당 사용자의 팔로워를
/users/<id>/followers
를 통해서 불러옵니다.
REST를 사용하게 되면 이렇게 각각의 Endpoint들에 총 세 번의 요청을 보내야 합니다.
[GET]
1. /users/<id>
{
"user": {
"id": "erasase1f123aw",
"name": "Jay",
"address": "...",
"email": "jay@jay.com"
}
}
2. /users/<id>/posts
{
"posts": [
{
"id": "ab123abasd12345",
"title": "Today I Learned",
"content": "....",
"comments": ["..."]
},
...
]
}
3. /users/<id>/followers
{
"followers": [
{
"id": "zxcv98420z09zxcv",
"name": "May",
"address": "...",
"email": "May@May.com"
},
...
]
}
대략적으로 이런 형태의 요청을 보내고, 데이터가 반환될 겁니다. 게다가 필요한 건 게시글의 제목
과 팔로워의 이름
인데, 쓸모없는 id
나 email
등의 정보까지 받아오게(overfetch) 됩니다.
그렇다면 GraphQL은 어떨까요?
query {
User(id: "erasase1f123aw") {
name
posts {
title
}
followers(last: 3) {
name
}
}
}
{
"data": {
"User": {
"name": "Jay",
"posts": [
{
"title": "Today I Learned"
}
],
"followers": [
{ "name": "May" },
{ "name": "Qay" },
{ "name": "Lay" }
]
}
}
}
이렇게 정확하게 원하는 내용에 대해서만 요청을 보내고, 데이터를 받아볼 수 있게 됩니다. 게다가 딱 하나의 쿼리로 모든 정보를 알 수 있게 되죠. Query를 보내 존재하는 Data를 조회할 수 있고, Mutation을 통해서 Data를 생성, 수정, 삭제하는 것도 가능합니다. 문법 자체는 Query나 Mutation이 다르지 않기 때문에 어렵지 않게 사용할 수 있습니다.
이러한 GraphQL은 Frontender에게 특히 편리합니다. REST API를 사용하면서 겪을 수 있는 Over/Underfetch를 비롯해 많은 불편함과 단점을 해결해주기 때문입니다. 물론 Data가공에 연산이 필요할 수는 있겠지만, 그러한 연산에 필요하게 되는 자원은 Server가 대신 처리해주면 되니까요. 위에서도 살펴봤듯이 REST API를 사용하여 Data를 가져올 때는, 다음과 같은 4단계의 작업을 합니다.
- HTTP 요청을 보내고,
- 서버에서 응답을 받은 뒤 그것을 해석하고,
- Data를 Local에 보관한 뒤,
- UI에 알맞은 Data를 표시한다.
하지만 GraphQL의 경우에는, 다음과 같이 2단계로 줄일 수 있게 됩니다.
- 필요한 Data를 서술해 요청한 뒤,
- 응답을 통해 받은 Data를 UI에 표시한다.
GraphQL Client
의 주된 역할은 앞서 말했듯이 선언적인 방식으로 Data를 불러오고 갱신할 수 있도록 해준다는 점입니다. 가령 기존에 API를 사용하고자 HTTP 요청을 사용했다면, GraphQL에서는 요구사항을 선언하는 Query를 작성해서 보내면 요청을 전송하고 응답받는 작업은 알아서 진행됩니다. 이러한 GraphQL Client에서 가장 널리 사용되는 것이 Apollo
입니다. Apollo에 대해서는 앞으로 기회가 된다면 별도로 작성해볼게요.
GraphQL Client가 있다면 당연히 Server도 존재하는데요, 실질적인 API의 구현체는 모두 이곳에서 나타나게 됩니다. 각 쿼리가 그 결과로 어떻게 변환되는지에 대한 실제 실행 알고리즘을 규정하고 있는데, Query의 모든 field가 순회되면서 각 field에 대해 resolver
를 실행하게 됩니다. 이러한 GraphQL Server부분, 정확히는 ORM 부분에서 널리 사용되는 라이브러리가 Prisma
입니다. Prisma는 Query Resolving을 처리해주는 계층을 제공하여, Server로 들어온 Query를 Prisma에 전달하고, 이를 실제 DB에 맞춰서 Resolve 하는 식으로 구현하게 됩니다. Prisma에 대해서도 앞으로 기회가 된다면 더욱 자세하게 작성해보겠습니다.