• 본 시리즈에서는 How to GraphQL의 Tutorial 문서들을 차례대로 번역합니다.
  • 이 글은 GraphQL Advanced - Server을 번역한 글입니다.
  • 오역 또는 의역이 있을 수 있습니다. 양해 부탁드리며, 수정할 필요한 부분은 댓글로 요청해주세요.

서버

GraphQL은 종종 프론트엔드에 특화된 API 기술이라고 일컬어지곤 합니다. 왜냐하면 GraphQL을 사용하면 데이터를 불러오는 클라이언트 측의 작업이 훨씬 편리해지기 때문입니다. 하지만 API 자체는 당연히 서버 측에서 구현되며, 서버에서 경험할 수 있는 장점 또한 많습니다. GraphQL을 사용하면 서버 개발자는 특정 엔드포인트를 구현하거나 최적화하는 작업보다는 사용 가능한 데이터를 서술하는 데에 보다 집중할 수 있게 됩니다.

GraphQL 실행

GraphQL은 단순히 스키마를 서술하고, 해당 스키마로부터 데이터를 불러올 수 있는 쿼리 언어를 서술하는 역할만을 맡지 않습니다. GraphQL은 각 쿼리가 쿼리 결과로 어떻게 변환되는지에 대한 실제 실행 알고리즘을 규정합니다. 이 알고리즘의 핵심은 꽤 간단합니다. 쿼리의 모든 필드가 순회되면서 각 필드에 대하여 "리졸버"가 실행됩니다. 아래의 스키마를 한번 보도록 합시다.

type Query {
  author(id: ID!): Author
}

type Author {
  posts: [Post]
}

type Post {
  title: String
  content: String
}

위의 스키마를 사용하는 서버에는 아래와 같은 형태의 쿼리를 보낼 수 있습니다.

query {
  author(id: "abc") {
    posts {
      title
      content
    }
  }
}

제일 먼저, 쿼리 내의 각 필드마다 대응하는 타입을 연관지을 수 있다는 점에 주목하시기 바랍니다.

query: Query {
  author(id: "abc"): Author {
    posts: [Post] {
      title: String
      content: String
    }
  }
}

이제 서버 측에서는 각 필드에 필요한 리졸버를 쉽게 파악할 수 있습니다. 쿼리의 실행은 쿼리 타입에서 시작하여 너비-우선으로 이루어집니다. 즉, 위의 쿼리에서는 Query.author에 대한 리졸버가 가장 먼저 실행됩니다. 다음으로, 리졸버의 결과를 받아서 다음 자식 요소인 Author.posts의 리졸버에 전달합니다. 이번에는 결과가 리스트이므로, 한번에 한 항목씩 리졸버가 실행됩니다. 따라서 아래와 같은 과정을 거치게 됩니다.

Query.author(root, { id: 'abc' }, context) -> author
Author.posts(author, null, context) -> posts
for each post in posts
  Post.title(post, null, context) -> title
  Post.content(post, null, context) -> content

이 과정을 모두 끝마친 뒤에는 모든 결과를 모아서 올바른 형태로 정리한 뒤 이를 반환합니다.

실제로 구현된 GraphQL 서버의 대부분은 "기본 리졸버"들을 제공하므로, 모든 필드들에 대하여 일일이 리졸버 함수를 정해줄 필요는 없다는 사실을 유의하시기 바랍니다. 예를 들어 GraphQL.js을 사용하는 경우, 리졸버의 상위 객체가 올바른 이름으로 된 필드를 포함하고 있다면, 리졸버를 정해주지 않아도 됩니다.

GraphQL의 작동 과정에 대하여 보다 자세히 알고 싶다면 Apollo 블로그의 "GraphQL 자세히 알아보기" 글을 읽어보시기 바랍니다.

일괄 리졸브 작업

위에서 설명한 GraphQL의 실행 전략을 잘 살펴보면 다소 순진하다는 것을 알 수 있습니다. 예를 들어, 백엔드 API 또는 데이터베이스로부터 데이터를 불러오는 리졸버가 있고, 해당 백엔드는 쿼리가 한번 실행될 때에 여러 차례 호출될 수 있는 상황을 가정해봅시다. 이때, 여러 게시물들 각각의 작성자를 불러와야 합니다. 이를 위한 쿼리는 아래와 같습니다.

query {
  posts {
    title
    author {
      name
      avatar
    }
  }
}

만약 이 게시물이 어떤 블로그의 게시물이라면, 동일한 작성자가 작성하였을 가능성이 높습니다. 각 게시물의 작성자에 대한 데이터를 불러오고자 API 호출을 해야 한다면, 여러 요청을 보내고서 동일한 데이터를 얻게 되는 실수를 범하게 됩니다. 예를 들면 아래와 같은 식입니다.

fetch('/authors/1')
fetch('/authors/2')
fetch('/authors/1')
fetch('/authors/2')
fetch('/authors/1')
fetch('/authors/2')

이러한 문제를 어떻게 해결해야 할까요? 조금만 슬기롭게 데이터를 불러옵시다. 모든 리졸버가 실행되기까지 기다렸다가, 각 항목에 대하여 반드시 딱 한번씩만 불러오는 유틸리티 함수를 만들어서 사용하면 됩니다.

authorLoader = new AuthorLoader()

// 다수의 불러오기 작업을 쌓아둔다
authorLoader.load(1);
authorLoader.load(2);
authorLoader.load(1);
authorLoader.load(2);

// 이제 loader 함수는 최소한의 양으로 작업을 수행한다
fetch('/authors/1');
fetch('/authors/2');

좀 더 좋은 방법이 없을까요? 있습니다. API가 일괄 요청을 지원한다면, 아래와 같은 식으로 불러오기 요청을 딱 한번만 하면 됩니다.

fetch('/authors?ids=1,2')

이것 역시 위의 로더 함수와 같은 형태로 캡슐화할 수 있습니다.

JavaScript에서는 위와 같은 전략을 구현하기 위하여 DataLoader와 같은 유틸리티 라이브러리를 사용하며, 다른 언어에도 유사한 기능을 하는 유틸리티가 존재합니다.

Quiz

다음 중 GraphQL 서버에 대한 설명으로 옳은 것은?

  • 한번 쿼리가 완료된 데이터는 다시 불러오지 않는다.
  • 쿼리의 필드들은 깊이 우선으로 리졸브가 이루어진다.
  • 리졸버는 모든 필드에 대하여 정의될 수 있다.
  • DataLoader를 사용하지 않는 GraphQL 서버는 GraphQL 서버가 아니다.