본문 바로가기

배움의 즐거움/프로그래밍

(5) GraphQL - 실행

반응형

Execution



유효성을 검사한 뒤 GraphQL 쿼리는 GraphQL 서버에 의해 실행되는데, 이 서버는 요청된 쿼리와 같은 형태로 결과를 리턴한다. 일반적으로 이는 JSON 이다.


GraphQL 은 타입 시스템이 없는 쿼리는 실행할 수 없는데, 예시를 통해 살펴보자. 아래 예시들은 지금까지 써왔던 예시들의 일부이다.

type Query {
  human(id: ID!): Human
}

type Human {
  name: String
  appearsIn: [Episode]
  starships: [Starship]
}

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

type Starship {
  name: String
}


쿼리가 실행될 때 어떤 일들이 발생하는지 알아보기 위하여 예시를 보자.


GraphQL 쿼리의 각 필드를 이전의 타입을 다음 타입으로 돌려주는 함수라고 생각하면 된다. 실제로 이는 GrapQL이 동작하는 원리와 같다. 각 타입의 각 필드는 resolver 이라고 불리는 함수로 뒷받침된다.


필드가 문자열, 숫자와 같이 스칼라 값을 생성한다면, 실행은 거기서 완료된다. 그러나 필드가 객체 값을 리턴한다면, 쿼리는 해당 객체에 적용되는 또 다른 필드를 포함하게 될 것이다. 이것은 해당 값이 스칼라 값에 미칠때까지 계속된다. GraphQL 쿼리는 언제나 스칼라 값에서 종료된다.

Root fields & resolvers  


모든 GraphQL 서버에서 가장 위의 레벨은 GraphQL API 의  가능한 모든 엔트리 포인트를 나타낸다. 이는 Root Query 라고 불린다.


아래 예시에서 Query 타입은 human 이라는 필드를 제공하는데, 이는 id 를 argument로 받는다. 이 필드의 resolver 함수는 데이터베이스에 접근하여 Human 객체를 구성한 뒤 리턴한다.

Query: {
  human(obj, args, context, info) {
    return context.db.loadHumanByID(args.id).then(
      userData => new Human(userData)
    )
  }
}

이 예시는 자바스크립으로 작성되었느나 GraphQL 서버는 다양한 언어들로 만들어 질 수 있다. resolver 함수는 4가지 argument 를 받는다.

Asynchronous resolvers  - 비동기적 resolvers


아래 resolver 함수에서 무슨 일이 발생하고 있는지 봐보자.

human(obj, args, context, info) {
  return context.db.loadHumanByID(args.id).then(
    userData => new Human(userData)
  )
}

context 는 데이터베이스에 접근할 수 있는 권한을 제공한다. 이는 GraphQL 쿼리에서 argument 로 제공된 id에 대한 사용자를 로드하는데 사용된다. 데이터베이스에서 로딩하는 과정이 비동기적 작업이므로 이는 Promise 를 리턴한다. 


자바스크립트에서 Promise 는 비동기 값으로 작업하는데 사용되는데, 다른 언어에서도 비슷한 컨셉이 있긴 하다. 데이터베이스가 값을 반환하면, 우리는 Human 객체를 구성한 뒤 리턴할 수 있다.


resolver 함수는 Promise 를 알아야 하는 반면에, GraphQL 쿼리는 이를 알 필요가 없다. 이것은 단순히 name 필드에 대해서 물어볼 수 있는 무언가를 human 필드가 리턴하기를 예상한다.

Trivial resolvers  


이제 human객체가 사용 가능하므로, GraphQL 요청된 필드를 실행할 수 있다.

Human: {
  name(obj, args, context, info) {
    return obj.name
  }
}

GraphQL 서버는 타입 시스템에 의해 동작하는데 이는 다음에 무엇을 할 지 결정하는데 사용된다. 심지어 human 필드가 어떠한 값이던 리턴하기 이전에, GraphQL 은 다음 단계에 대해서 알고 있다. 이 다음 단계는 Human 타입이 가지고 있는 필드를 resolve 하는 것인데, 이는 타입 시스템이 human 필드가 Human 을 리턴할 것이라는 것을 말해주기 때문이다.


이름을 resolve 하는 것은 이 경우에는 직관적이다. 이름 resolver 함수가 불리우고 obj 는 이전 필드에서 반환된 new Human 객체이다. 이 경우에는 우리는 Human 객체가 읽고 리턴할 수 있는 name 이라는 속성을 가질 것으로 예상한다.


실제로, 많은 GraphQL 라이브러리는 이 정도로 간단한 resolver 를 생략할 수 있도록 할 것이다. 그리고 만약에 resolver 가 필드에서 제공되지 않는다면, 같은 이름의 속성이 읽어지고 리턴될 것이다.

module Types UserType = GraphQL::ObjectType.define do name 'User' field :id, !types.ID field :email, !types.String # 예를 들어 여기에서는 따로 resolver 생략하였지만 email 필드를 자동적으로 반환 end end

Scalar coercion


name 필드가 resolved 되는 동안, appearsIn 과 starships 필드도 동시에 resolved 될 수 있다.
Human: {
  appearsIn(obj) {
    return obj.appearsIn // returns [ 4, 5, 6 ]
  }
}

우리의 타입 시스템이 appearsIn 이 Enum 값들을 리턴할 것이라고 주장하였지만 해당 함수는 숫자들을 리턴한다. 그러나 실제로  결과를 보면 적당한 enum 값들이 리턴된 것을 볼 수 있다. 무슨 일이 일어난걸까?


이것은 스칼라 coercion 의 예시이다. 타입 시스템은 무엇을 예상해야 되는지 알고 있고, resolver 함수에 의해 리턴된 값들을 API에 맞게 변환한다. 이 경우에는, GraphQL 타입 시스템에서는 Enum 값으로 표현이 되지만 서버에 정의된 Enum 값들이 내부적으로는 4,5,6 과 같은 숫자일 것이다. 

List resolvers  


우리는 필드가 리스트를 리턴할 때 어떤일이 일어나는지 위의 appearsIn 필드를 통해 보았다. 이것은 enum 값들의 리스트를 반환하였는데, 타입 시스템의 예상대로 각 아이템이 적당한 enum 값으로 강제 변환되었다. 그러면 starships 필드를 resolve 할때는 무슨 일이 발생할까?

Human: {
  starships(obj, args, context, info) {
    return obj.starshipIDs.map(
      id => context.db.loadStarshipByID(id).then(
        shipData => new Starship(shipData)
      )
    )
  }
}

이 필드에 해당하는 resolver 는 단순히 하나의 Promise 만 리턴하는 것이 아니라 Promise 리스트를 리턴한다. Human 객체는 그들이 조종한 Starship id 들의 리스트를 가지고 있는데, 우리는 실제 Starships 객체를 가져오기 위해서 모든 id 를 로드해야 한다.

GraphQL will wait for all of these Promises concurrently before continuing, and when left with a list of objects, it will concurrently continue yet again to load the name field on each of these items. -?

Producing the result  


각 필드가 해결되면서, 결과 값은 필드명(혹은 alias)과 함께 key-value 로 맵핑된다. 이 과정은 쿼리의 최하단 leaf 필드부터 root 쿼리타입까지 쭉 올라오면서 진행된다. 이는 기존의 쿼리와 같은 형태의 구조를 제공하고, 이는 이를 요청한 클라이언트 측에 일반적으로 JSON 형태로 반환된다.


위의 resolver 함수들이 어떻게 결과를 생성했는지 보자.

* 해당 글은 번역기 돌리다가 크롬 번역기 말도 안되는 해석에 지친 본인이 나중에 참고할 의도로 대충대충 발로 해석한 것이니 참고용으로만 사용하시길 바랍니다.

* 출처: https://graphql.github.io/learn/execution/

반응형