GraphQL - Graphene: 3(Resolvers)

Jihun Kim·2022년 4월 26일
0

GraphQL

목록 보기
10/16
post-thumbnail

Resolvers

  • Resolver는 스키마의 필드에 해당하는 데이터를 가져와서 쿼리 하는 데 도움을 주기 위한 메소드이다.
  • Resolver는 lazy 실행이기 때문에 만약 resolver가 정의된 필드가 쿼리에 포함되지 않는다면 해당 resolver는 실행되지 않는다.
  • resolver 메소드는 필드명과 이름이 일치해야 한다.
    - 만약 필드명이 name이라면 resolver 메소드의 이름은 resolve_full_name이 되어야 한다.

Resolver parameters

Parent Value Object (parent)

  • 이는 일반적으로 ObjectType의 대부분의 필드에 대한 값을 파생시키는 데 사용 된다.
  • 루트의 Query 필드와 같은 상위 필드(parent field)가 없는 경우, 상위 값(parent value)은 쿼리를 실행하는 동안 구성된 root_value로 설정 된다(default=None).

아래의 예시로 살펴보는 것이 더 쉽다.

from graphene import ObjectType, String, Field

class Person(ObjectType):
    full_name = String()

    def resolve_full_name(parent, info):
        return f"{parent.first_name} {parent.last_name}"

class Query(ObjectType):
    me = Field(Person)

    def resolve_me(parent, info):
        # returns an object that represents a Person
        return get_human(name="Luke Skywalker")
  1. parent: 쿼리 실행의 root_value로 설정 된다.
    • 여기서는 None이다.
  2. Query.resolve_me: 인자로 parent=None이 들어가며 이 때 객체인 Person("Luke", "Skywalker")가 반환 된다.
    • 이 객체는 Person.resolve_full_name 메소드가 호출될 때 parent의 값으로 들어 간다.
  3. resolve_full_name은 스칼라를 반환하며 반환되는 String은 "Luke Skywalker"이다.
  • 이 과정에서 알 수 있듯이, resolver는 parent 인자의 value로 들어가는 Parent Value Object를 반환하며 이 객체는 다음 resolver의 실행에 사용 된다.
    - 이 때 만약 value가 스칼라면 이미 leaf node에 도달했기 때문에 응답을 반환 한다.
  • 참고로, parentobj, source라는 이름으로 사용될 수 있다.
    - 또한, resolve 될 객체의 이름을 딴 이름이 사용될 수도 있다: root Query의 경우 root, Person value object의 경우 person

GraphQL Execution Info(info)

  • info 인자는 현재 GraphQL 쿼리 실행에 대한 메타 정보 참조용(fields, schema, parsed query 등)으로 사용 된다.
  • info 인자를 통해 사용자 인증, 데이터 로더 인스턴스 또는 쿼리를 해결하기 위해 유용한 모든 것을 저장하는 데 사용할 수 있는 요청별 컨텍스트에 접근이 가능하다.

GraphQL Arguments(kwargs)

  • 필드의 속성으로 정의 된 어떤 인자든 resolver 메소드의 keyword arguments로 전달될 수 있다.

아래의 예시를 보면, name이 **kwargs에 해당한다.

from graphene import ObjectType, Field, String

class Query(ObjectType):
   human_by_name = Field(Human, name=String(required=True))

   def resolve_human_by_name(parent, info, name):
       return get_human(name=name)

또는 아래와 같이 타입의 속성으로 args를 사용해 정의된 값을 전달할 수도 있다.
- 여기서는 description

from graphene import ObjectType, Field, String

class Query(ObjectType):
    answer = String(args={'description': String()})

    def resolve_answer(parent, info, description):
        return description

Graphene Resolvers의 특징

Implicit staticmethod

  • Graphene에서는 모든 resolver methods를 암묵적으로 staticmethod로 취급한다.
    - 즉, 파이썬의 다른 class method와는 달리 resolver의 첫 번째 인자는 self가 아니다.
  • resolver methods의 첫 번째 인자는 항상 parent이다.
    - Grapehen에서 이렇게 사용하는 이유는 아마도 우리는 GraphQL에서 쿼리를 resolve 하기 위해 어떤 parent value object를 사용할 지에 더 집중하기 때문일 것이다.
from graphene import ObjectType, String

class Person(ObjectType):
    first_name = String()
    last_name = String()

    @staticmethod
    def resolve_first_name(parent, info):
        '''
        실제로 resolve method를 사용하기 위해 @staticmethod 데코레이터를 
        명시적으로 사용할 필요는 없다!
        '''
        return parent.first_name

Default Resolver

  • 만약 ObjectType의 field 속성에 대한 resolver method가 정의되어 있지 않다면 Graphene은 default resolver를 대신 제공한다.
    - 예를 들어 parent 인자의 타입이 dictionary라면 resolver는 field name과 일치하는 dictionary key를 알아서 찾는다.

GraphQL argument defaults

  • non-null field의 경우 인자로 required=True를 넘겨주어야 한다.
    - 그런데 만약 반드시 전달 되어야 하는 필드가 전달 되지 않는다면 name=None이 resolver method로 전달 되는 것이 아니라 에러가 발생한다.

[문제 상황]

스키마

  from graphene import ObjectType, String

  class Query(ObjectType):
      hello = String(required=True, name=String())

      def resolve_hello(parent, info, name):
          return name if name else 'World'

쿼리

  query {
      hello
  }

에러

  • name이 전달 되지 않아서 아래와 같은 에러가 발생한다.
  TypeError: resolve_hello() missing 1 required positional argument: 'name'

[해결책]
1. 모든 keyword argument를 dict로(**kwargs 이용) 묶는다.

  from graphene import ObjectType, String

  class Query(ObjectType):
      hello = String(required=True, name=String())

      def resolve_hello(parent, info, **kwargs):
          name = kwargs.get('name', 'World')
          return f'Hello, {name}!'
  1. resolver method의 인자에 default value를 준다.
  from graphene import ObjectType, String

  class Query(ObjectType):
      hello = String(required=True, name=String())

      def resolve_hello(parent, info, name='World'):
          return f'Hello, {name}!'
  1. 스키마의 인자에 default value를 준다.
  from graphene import ObjectType, String

  class Query(ObjectType):
      hello = String(
          required=True,
          name=String(default_value='World')
      )

      def resolve_hello(parent, info, name):
          return f'Hello, {name}!'

Resolver outside the class

  • field에 class 외부에서 정의된 resolver를 추가할 수 있다.
    - 프로젝트 규모가 커지면 사용하기 좋은 방법인 것 같다.
from graphene import ObjectType, String

def resolve_full_name(person, info):
    return f"{person.first_name} {person.last_name}"

class Person(ObjectType):
    first_name = String()
    last_name = String()
    full_name = String(resolver=resolve_full_name)

Meta class를 이용한 ObjectType configuration

Type name & Description

  • Type name
    - 따로 지정하지 않으면 ObjectType을 정의하는 클래스명과 동일하다.
  • Description
    - ObjectType에 대한 schema description은 docstring 또는 Meta class에 정의된 description을 이용할 수 있다.
from graphene import ObjectType

class MyGraphQlSong(ObjectType):
  ''' We can set the schema description for an Object Type here on a docstring '''

    class Meta:
        name = 'Song'  
        description = 'But if we set the description in Meta, this value is used instead'

Interfaces & Possible Types

  • interfaces: 해당 객체가 구현하는 GraphQL interfaces를 정의한다.
  • possible_types: interfaces 또는 Union과 같은 애매한 타입을 resolve 하는 데 도움을 줄 수 있다.
  from graphene import ObjectType, Node

  Song = namedtuple('Song', ('title', 'artist'))

  class MyGraphQlSong(ObjectType):
      class Meta:
          interfaces = (Node, )
          possible_types = (Song, )
profile
쿄쿄

0개의 댓글