[Django] GraphQL 기초 한 방 정리
Web/Django

[Django] GraphQL 기초 한 방 정리

뉴비뉴 2020. 12. 12.

목차

  • 들어가기 전에
  • GraphQL 정의
  • GraphQL vs REST API
    - GraphQL 장점
    - GraphQL 단점
  • Django에서 GraphQL 실습
    - Schema
    - resolver
    - Query

들어가기 전에

평소 Django를 사용하면서 RESTful API framework를 주로 사용해왔습니다.

그러던 중 최근 사이드 프로젝트를 진행하면서 GraphQL에 관심을 갖게 되어 찾아보게 되었습니다. GraphQL을 배우면 배울수록 RESTful API를 사용할 때는 몰랐던 불편한 점들이 하나둘씩 보이기 시작했습니다.

 

들어가기 전에 RESTful API에 대해 간단하게 알아보도록 하겠습니다.

REST API란 Web상에 HTTP 프로토콜을 활용한 Application Protocol Interface입니다.

출처: https://www.seobility.net/en/wiki/REST_API

1. 클라이언트 단에서 서버 단으로 어떠한 데이터 요청을 보내게 됩니다.

2. 서버 단에서는 클라이언트에서 접근할 수 있도록 URL 주소(Endpoint)를 열어놓게 됩니다.

 

URL 경로로 요청을 보낼 때 어떻게 데이터 format은 어떻게 보내야 하는지, 서버 단에서는 응답을 보낼 때 어떤 식으로 데이터 format 응답을 보낼 것인지를 보내주는지 만들어놓은 것을 RESTful API라고 합니다.

 

GraphQL에 대해 알아보고, Django에서 GraphQL 사용방법에 대해서 알아보도록 하겠습니다.

GraphQL 정의

GraphQL은 쿼리 언어입니다. 여기서 말하는 쿼리 언어는 우리가 흔히 알고 있는 SQL 같은 쿼리 언어라는 것입니다.

같은 쿼리 언어라고 해서 GraphQL이 데이터베이스에서 데이터를 가져오는 언어라는 의미는 아닙니다.

GraphQL은 웹 클라이언트에서 데이터를 효과적으로 가져오는 것이 주목적입니다. 그래서 GraphQL은 클라이언트 시스템에서 주로 작성되고 호출됩니다. 예를 들면 Vue.js 와 Django 간 API 통신을 한다고 하였을 때 GraphQL이 작성되는 곳은 Vue.js(웹 클라이언트)측입니다.

GraphQL vs REST API

REST API는 URL, METHOD 등을 조합하기 때문에 다양한 Endpoint가 존재합니다. 반면, gql은 단 하나의 Endpoint가 존재합니다. 또한, gql API에서는 불러오는 데이터의 종류를 쿼리 조합을 통해서 결정합니다. 예를 들면, REST API에서는 각 Endpoint마다 데이터베이스 SQL 쿼리가 달라지는 반면, gql API는 gql 스키마의 타입마다 데이터베이스 SQL 쿼리가 달라집니다.

GraphQL의 장점

  1. 엔트포인트가 한 개다.
    REST API의 엔드포인트는 만드는 사람의 구조에 따라 달라질 수는 있지만 크게 기능 별로 엔드포인트를 만들게 됩니다.
    유저와 게시글이라는 기능이 있다면 /users, /posts라는 엔드포인트가 생기게 되는 것이죠. 하지만 GraphQL의 경우에는 /graphql이라는 엔드포인트 하나만 있으면 됩니다.
  2. 한 번의 요청으로 내가 원하는 데이터를 가져올 수 있다.
    smile이라는 유저가 작성한 게시글을 가져오고 싶다면 백엔드 개발자에게 엔드포인트를 하나 만들어 달라고 하거나, 프론트 개발자가 /users, /posts에 요청을 보내서 데이터를 받은 뒤 데이터를 가공하여 사용할 수 있습니다. 하지만 GraphQL은 한 번의 요청에 내가 받고자 하는 데이터를 쿼리로 작성하여 요청을 보내면 내가 원하는 데이터를 받을 수 있습니다.
    서버 수정 없이 클라이언트가 원하는 쿼리를 날릴 수 있어 API를 설계하거나 변경하는 비용을 낮출 수 있습니다.
  3. 인트로스팩션(introspection) 기능
    기존 서버-클라이언트 협업 방식에서는 연동 규격서라고 하는 API 명세서를 주고받는 절차가 반드시 필요했습니다. 프로젝트 관리 측면에서 관리해야 할 대상의 증가는 작업의 복잡성 및 효율성 저해를 의미합니다. 이 API 명세서는 때때로 관리가 제대로 되지 않아, 인터페이스 변경 사항을 제때 문서에 반영하지 못하기도 하고, 제 타이밍에 전달 못하곤 합니다.
    이러한 REST의 API 명세서 공유와 같은 문제를 해결하는 것이 gql의 인트로스펙션 기능입니다. gql의 인트로스펙션은 서버 자체에서 현재 서버에 정의된 스키마의 실시간 정보를 공유를 할 수 있게 합니다. 이 스키마 정보만 알고 있으면 클라이언트 사이드에서는 따로 연동 규격서를 요청할 필요가 없게 됩니다. 클라이언트 사이드에서는 실시간으로 현재 서버에서 정의하고 있는 스키마를 의심할 필요 없이 받아들이고, 그에 맞게 쿼리문을 작성하면 됩니다

    이 말은 즉, 프론트엔드 개발자와 백엔드 개발자 간에 커뮤니케이션이 줄어들면서 생산성이 높아지게 된다는 것을 의미합니다.

Django GraphQL 라이브러리에서 제공해주는 인트로스팩션 페이지

GraphQL 단점

GraphQL의 단점과 대안은 다른 블로그에서 너무나도 잘 설명해주셔서 자세한 설명은 링크를 클릭하여 보시기 바랍니다.

  1. HTTP 캐싱 사용 불가능
  2. 직접 구현해야 하는 파일 업로드
  3. 요청 필터링의 어려움

Django에서 GraphQL 실습

graphene-django를 사용하여 Django에서 GraphQL을 사용해보도록 하겠습니다.

실습 튜토리얼은 graphene-django에서 제공해주는 튜토리얼을 따라 진행하겠습니다.

 

docs.graphene-python.org/projects/django/en/latest/tutorial-plain/#basic-tutorial

 

Graphene-Python

Basic Tutorial Graphene Django has a number of additional features that are designed to make working with Django easy. Our primary focus in this tutorial is to give a good understanding of how to connect models from Django ORM to Graphene object types. Set

docs.graphene-python.org

Schema

튜토리얼을 진행하다 보면 Schema를 생성하는 파트가 있습니다.

GraphQL Schema를 Django에서 사용하기 위해서는 schema.py에 graphene 라이브러리를 사용한 Schema가 정의되어야 합니다.

GraphQL을 사용한다는 것은 데이터의 관계를 그래프 형태로 나타낸다는 것입니다.

# cookbook/schema.py


import graphene
from graphene_django import DjangoObjectType

from cookbook.ingredients.models import Category, Ingredient

class CategoryType(DjangoObjectType):
    class Meta:
        model = Category  # Model
        fields = ("id", "name", "ingredients")  # 보여질 Field를 지정

class IngredientType(DjangoObjectType):
    class Meta:
        model = Ingredient
        fields = ("id", "name", "notes", "category")

# class Query를 통해서 데이터에 접근할 수 있습니다.
class Query(graphene.ObjectType):
    all_ingredients = graphene.List(IngredientType)
    category_by_name = graphene.Field(CategoryType, name=graphene.String(required=True))

    def resolve_all_ingredients(root, info):  # resolve_<field_name>
        # We can easily optimize query count in the resolve method
        return Ingredient.objects.select_related("category").all()

    def resolve_category_by_name(root, info, name):
        try:
            return Category.objects.get(name=name)
        except Category.DoesNotExist:
            return None

schema = graphene.Schema(query=Query)

DjangoObjectType은 Django에서 생성된 모델을 ObjectType으로 변환시켜줍니다. 추가적으로 모델의 모든 필드를 보여주기 때문에 위 코드 블록처럼 fields를 지정하여 모델이 변경될 때 의도하지 않게 데이터가 노출될 가능성을 줄여주는 것이 좋습니다.

Resolvers

Schema 개체가 GraphQL 쿼리를 수신하면 관련된 Resolver에 매핑합니다.

resolve_<field_name>으로 시작하는 메서드는 일치하는 필드로 자동으로 바인딩됩니다.

resolve_<field_name>의 인자에는 root, info, **args 가 있습니다.

  • root는 부모 resolver가 리턴한 객체를 가리킵니다. 
  • info는 모든 resolve 메서드에 전달된 유용한 정보(context)가 있습니다.
    info.context 속성은 Django 개발자에게 친숙한 HTTPRequest 객체입니다. 인증된 사용자 확인과 같은 resolve 메서드에서 Django의 HTTPRequest의 전체 기능이 제공됩니다.
  • args는 말 그대로 Argument입니다. 위 예시를 보시면 category_by_name에 끝에 name이라는 Args를 받고 있고, String데이터가 들어와야 되고, 항상 데이터를 넣어줘야 된다고 정의되어 있습니다. Query에서 작성 시에 넣어진 args는 resolve에서 받아 사용할 수 있습니다.

Query

GraphQL은 쿼리 언어입니다. 우리는 아래와 같은 쿼리를 작성하여 원하는 데이터를 가져올 수 있습니다.

query {
  allIngredients {
    id
    name
  }
}

class Query에서 작성한 all_ingredients라는 필드를 camel Case로 변경하여 입력하겠습니다. 그 안에 어떤 필드의 값을 가져올지 명시하면 Ingredient 테이블에 있는 모든 id, name을 가져옵니다.

{
  "data": {
    "allIngredients": [
      {
        "id": "1",
        "name": "Eggs"
      },
      {
        "id": "2",
        "name": "Milk"
      },
      {
        "id": "3",
        "name": "Beef"
      },
      {
        "id": "4",
        "name": "Chicken"
      }
    ]
  }
}

다음은 ForeignKey로 연결되어있는 데이터를 가져와보겠습니다.

query {
  categoryByName(name: "Dairy") {
    id
    name
    ingredients {
      id
      name
    }
  }
}

argument로 name을 입력해주고, Category 테이블에 있는 id, name, ingredients를 출력하는데 ingredients는 ForeignKey로 연결되어 있기 때문에 ingredients 모델에서 id와 name을 가져오라는 쿼리가 만들어졌습니다.

 

출력 결과는 어떻게 나올까요?

Category의 name이 "Dairy"인 id와 name, ingredients 모델 안에 id, name이 출력될 것입니다.

{
  "data": {
    "categoryByName": {
      "id": "1",
      "name": "Dairy",
      "ingredients": [
        {
          "id": "1",
          "name": "Eggs"
        },
        {
          "id": "2",
          "name": "Milk"
        }
      ]
    }
  }
}

공식 문서를 찾아보니 이 외에도 활용할 수 있는 방법이 정말 많습니다. 페이지 네이션을 사용할 수도 있고, Django의 get_queryset 메서드를 정의하여 Query 개체 수준 대신 ObjectType 수준에서 필터링을 사용할 수도 있고, django-filter를 사용하여 필드 별로 필터링을 사용할 수도 있습니다. 인증 관련해서는 django-greaphql-auth를 제공하여 JWT 토큰을 사용하여 Client의 접속을 JWT 토큰으로 관리할 수 있습니다.

마무리

GraphQL을 사용한 경험이 아직 풍부하지 않아 글에서는 기본적인 내용만 다루었습니다.

백엔드 개발자 입장에서 GraphQL은 사용 안 할 이유가 없어 보였습니다. 자동으로 문서를 만들어주는 인트로스팩션 기능이 너무나도 유용해서 프론트엔드 개발자는 인트로스팩션으로 만들어진 Docs를 보면서 쿼리를 만들어 요청을 보내면 되니 백엔드 개발자로서는 기존에 엔드포인트를 하나 만들고, Swagger를 만들어서 format과 설명을 달아주는 작업이 사라지는 게 가장 큰 장점 같습니다.

앞으로 프로젝트를 진행하면서 페이지네이션과 JWT토큰, django-filter를 사용해보고 다음 편을 만들어보도록 하겠습니다.

 

감사합니다.

참고

- tech.kakao.com/2019/08/01/graphql-basic/

 

GraphQL 개념잡기

GraphQL은 페이스북에서 만든 쿼리 언어입니다. GrpahQL은 요즘 개발자들 사이에서 자주 입에 오르내리고 있으나, 2019년 7월 기준으로 얼리스테이지(early-stage)임은 분명합니다. 국내에서 GraphQL API를 O

tech.kakao.com

- docs.graphene-python.org/projects/django/en/latest/tutorial-plain/#basic-tutorial

 

Graphene-Python

Basic Tutorial Graphene Django has a number of additional features that are designed to make working with Django easy. Our primary focus in this tutorial is to give a good understanding of how to connect models from Django ORM to Graphene object types. Set

docs.graphene-python.org

- www.bangseongbeom.com/graphql-downsides-alternatives.html

 

GraphQL의 단점과 대안

GraphQL(그래프QL)은 서버 수정 없이 클라이언트가 원하는 쿼리를 날릴 수 있어 API를 설계하거나 변경하는 비용을 획기적으로 낮출 수 있습니다. 그러나 HTTP에서 제공하는 기존 인프라를 그대로 사

www.bangseongbeom.com

 

댓글

💲 추천 글