2022-06-29,   권재우

이번 포스팅에서는 이전에 설명했던 GraphQL을 실제로 구현해보는 포스팅을 하겠습니다. 만약 GraphQL의 개념이 궁금하신 분은 아래 링크를 눌러서 확인하시면 됩니다.
Link: GraphQL의 개념과 장단점

GraphQL 구조

아래 이미지를 통해 GraphQL의 구조를 확인할 수 있습니다. Screenshot_3

  • Resolver : 데이터 엑세스 및 조작을 위한 임의의 함수
  • Ariadne : GraphQL 쿼리언어를 구체화해주는 파이썬 라이브러리

클라이언트가 GraphQL쿼리를 통해서 데이터를 POST로 요청하면 API에서 쿼리를 받습니다. 그리고 그 쿼리를 Ariadne라는 라이브러리로 컴파일한 후 Resolver로 보내줍니다. Resolver는 DB에 접근해서 클라이언트가 요청한 데이터를 가져와서 return 해주는 구조로 되어있습니다.

GraphQL의 조회, 생성, 삭제, 수정 등 모든 요청은 POST로 처리합니다. 그 이유는 아래에서 설명하겠습니다.

GraphQL 쿼리

GraphQL API를 만들어보기에 앞서 GraphQL 쿼리에 대해서 알아보겠습니다.
그림1

GraphQL은 SQL을 바탕으로 만들어진 쿼리언어이기 때문에 SQL과 비교학습을 통해 보다 쉽게 이해할 수 있습니다.

위 그림처럼 원하는 테이블을 설정하고 가져올 데이터를 GraphQL쿼리로 작성하면 됩니다.

GraphQL이 POST 요청만 하는 이유

REST API에서는 수정, 조회가 필요할 때 여러번 요청해야 합니다. 하지만 GraphQL의 경우 한번의 쿼리요청으로 처리가 가능합니다.(아래그림참조)

Screenshot_4

UPDATE, SELECT 3번의 요청을 한번에 처리 이렇게 여러 요청을 한번에 처리할 수 있는 장점때문에 일반적으로 POST요청만 사용합니다.

GraphQL 구현

GraphQL 자체는 쿼리 언어이기 때문에 구체화 할 수 있는 언어와 라이브러리가 필요(아래 주소 참조)

GraphQL 공식홈 Link: GraphQL언어, 라이브러리

  • GraphQL 개발 언어로 Javascript를 많이 사용하지만 필자는 python을 사용애서 개발했습니다.

Screenshot_2

  • Python라이브러리로 “Ariadne”사용

Screenshot_5

GraphQL 구현 예시

  • REST API로 구현된 모델 조회기능(model, model_vsn)을 GraphQL로 구현해 보겠습니다.
    • DB는 Postgrasql의 Alertsys 로컬 DB사용

Screenshot_6

GraphQL 조회기능 구현

  • /model/model_id
  • /model/model_id/version

1. 설치

pip install flask ariadne flask-sqlalchemy flask-cors

2. Flask app 생성

아래 그림처러 디렉토리 구조를 만들었습니다.
Screenshot_7

api/init.py

from flask import Flask
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
import psycopg2

app = Flask(__name__)
CORS(app)

app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql+psycopg2://[DB_NAME]:[DB_USER]@[호스트주소]:[포트번호]/[DB_PASS]"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)

init.py는 위 코드로 작성하고 DB정보 파악해서 연결하기

app.py

from api import app, db
from ariadne import load_schema_from_path, make_executable_schema, \
    graphql_sync, snake_case_fallback_resolvers, ObjectType
from ariadne.constants import PLAYGROUND_HTML
from flask import request, jsonify

@app.route("/graphql", methods=["GET"])
def graphql_playground():
    return PLAYGROUND_HTML, 200
Flask run

Screenshot_9

Flask run 코드로 실행

http://127.0.0.1:5000/graphql

위 주소로 입력했을때 아래 그림처럼 나오면 Flask 정상 작동
Screenshot_8

위 GET요청의 Return페이지(PLAYGROUND_HTML)는 Ariadne 라이브러리에서 제공하는 쿼리실행 페이지입니다.

해당 페이지에서 쿼리를 통해 데이터를 확인할 수 있습니다.

3. Model 조회 코드 작성

  • GraphQL 스키마 코드 작성

schema.graphql

schema {
    query: Query
}

type Model {
    model_id: String!
    model_nm: String!
    model_type: String
    model_cmmt: String
    rgst_dt: String
    rgst_time: String
    rgstr_nm: String
    del_yn: Boolean
}

type ModelResult {
    success: Boolean!
    errors: [String]
    model: Model
}

type Query {
    getModel(model_id: String!): ModelResult!
}
  • 모델 만들기

api/models.py

from app import db

class Model(db.Model):
    model_id = db.Column(db.Integer, primary_key=True)
    model_nm = db.Column(db.String)
    model_type = db.Column(db.String)
    model_cmmt = db.Column(db.String)
    rgst_dt = db.Column(db.String)
    rgst_time = db.Column(db.String)
    rgstr_nm = db.Column(db.String)
    del_yn = db.Column(db.Boolean)

    def to_dict(self):
        return {
            "model_id": self.model_id,
            "model_nm": self.model_nm,
            "model_type": self.model_type,
            "model_cmmt": self.model_cmmt,
            "rgst_dt": str(self.rgst_dt),
            "rgst_time": str(self.rgst_time),
            "rgstr_nm": self.rgstr_nm,
            "del_yn": self.del_yn,
        }

DB에 있는 Model테이블 정보를 가져오는 코드 작성

  • resolver 구현

api/queries.py

from .models import Model

def getModel_resolver(obj, info, model_id):
    try:
        model = Model.query.get(model_id)
        payload = {
            "success": True,
            "model": model.to_dict(),
        }
    except AttributeError:  # todo not found
        payload = {
            "success": False,
            "errors": ["Post item matching {model_id} not found"]
        }
    return payload

getmodel코드는 “model_id”값을 입력하면 해당 model데이터를 조회하는 기능입니다.

  • POST 요청 API코드

app.py

from api import app, db
from ariadne import load_schema_from_path, make_executable_schema, \
    graphql_sync, snake_case_fallback_resolvers, ObjectType
from ariadne.constants import PLAYGROUND_HTML
from flask import request, jsonify
from api.queries import getModel_resolver

query = ObjectType("Query")
query.set_field("getModel", getModel_resolver)

type_defs = load_schema_from_path("schema.graphql")
schema = make_executable_schema(
    type_defs, query, snake_case_fallback_resolvers
)

@app.route("/graphql", methods=["GET"])
def graphql_playground():
    return PLAYGROUND_HTML, 200

@app.route("/graphql", methods=["POST"])
def graphql_server():
    data = request.get_json()
    success, result = graphql_sync(
        schema,
        data,
        context_value=request,
        debug=app.debug
    )
    status_code = 200 if success else 400
    return jsonify(result), status_code

기존 app.py코드에 위 코드로 변경

  • 실행
flask run
  • 확인

http://127.0.0.1:5000/graphql 로 접속해서 아래 쿼리로 정보 조회

query GetModel {
  getModel(model_id: "test1") {
    model {
      model_id
      model_nm
      model_type
      model_cmmt
      rgst_dt
      rgst_time
      rgstr_nm
      del_yn
    }
    success
    errors
  }
}

아래 이미지처럼 해당 model_id값에 대한 DB정보 가져올수 있습니다.

Screenshot_10

4. ModelVsn 조회 코드 작성

위 코드에서 각자 .py에 ModelVsn코드 추가

schema.graphql

type Model_vsn {
    model_id: String!
    model_vsn: String!
    rpstr_loc: String
    workflow_use_yn: Boolean
    del_yn: Boolean
    rgst_dt: String
    rgst_time: String
    rgstr_nm: String
}

type ModelvsnResult {
    success: Boolean!
    errors: [String]
    model_vsn : Model_vsn
}
type Query {
    getModel(model_id: String!): ModelResult!
    getModelVsn(model_id: String!, model_vsn: String!): ModelvsnResult!
}

api/queries.py

def getModelVsn_resolver(obj, info, model_id, model_vsn):
    try:
        model_vsn1 = rnn_model_vsn.query.get({"model_id":model_id, "model_vsn": model_vsn})
        payload = {
            "success": True,
            "model_vsn" : model_vsn1.to_dict()
        }
    except AttributeError:  # todo not found
        payload = {
            "success": False,
            "errors": ["Post item matching {id} not found"]
        }
    return payload

api/models.py

class rnn_model_vsn(db.Model):
    model_id = db.Column(db.Integer, primary_key=True)
    model_vsn = db.Column(db.String, primary_key=True)
    rpstr_loc = db.Column(db.String)
    workflow_use_yn = db.Column(db.Boolean)
    del_yn = db.Column(db.Boolean)
    rgst_dt = db.Column(db.String)
    rgst_time = db.Column(db.String)
    rgstr_nm = db.Column(db.String)

    def to_dict(self):
        return {
            "model_id": self.model_id,
            "model_vsn": self.model_vsn,
            "rpstr_loc": self.rpstr_loc,
            "workflow_use_yn": self.workflow_use_yn,
            "del_yn" : self.del_yn,
            "rgst_dt": str(self.rgst_dt),
            "rgst_time": str(self.rgst_time),
            "rgstr_nm": self.rgstr_nm,
        }

app.py

from api.queries import getModel_resolver, getModelVsn_resolver

query.set_field("getModelVsn", getModelVsn_resolver)
  • 실행
flask run
  • 확인

http://127.0.0.1:5000/graphql 로 접속해서 아래 쿼리로 필요한 정보만 fit하게 조회가 가능합니다.

  • model_id
  • model_vsn
  • rpstr_loc
query {
  getModelVsn(model_id: "test1", model_vsn: "v1") {
      model_vsn {
          model_id
          model_vsn
          rpstr_loc
      }
  }
}

아래 이미지처럼 해당 model_id값에 대한 DB정보를 가져올수 있습니다.

Screenshot_11

결론

위 코드로 model, model_vsn의 정보를 가져오는 코드를 만들어 보앗습니다.
기존 REST API로는

  • /model/model_id
  • /model/model_id/version

위 두개의 Endpoint로 요청을 했다면
GraphQL API는 /graphql로 하나로만 요청할 수 있었습니다.
또, 내가 원하는 데이터만 fit하게 들고 올 수 있었죠

GraphQL을 구현해보는 내용은 여기서 마치겠습니다. 아래는 GraphQL을 구현하면서 느낀 내용을 적어보았습니다.

과연 이러한 장점을 가지고 REST API개발을 GraphQL이 대체할 수 있을까요?
이 질문에 대해서 공식문서 내용을 가져왔습니다.

GraphQL이 REST를 완벽히 대체 할 수 있는가?

공식문서
No, not necessarily. They both handle APIs and can serve similar purposes from a business perspective. GraphQL is often considered an alternative to REST, but it’s not a definitive replacement.

-> GraphQL이 REST를 완벽히 대체불가(어려운 파일 전송 이슈)

GraphQL 전환 시 부작용

-> 혼합(GraphQL, REST)사용할 때 클라이언트의 불만(데이터 요청시 헷갈리고 GraphQL쿼리문을 새로 익혀야 함)

-> 기존 REST를 GraphQL로 변경할 때 드는 비용 대비 효과가 낮음(검증된 효과가 없음)

-> GraphQL은 기업에서 개발하는게 아닌 커뮤니티 드리븐 방식이기 때문에 확장성과 지속 가능성이 불확실

※ REST를 대체할 수 없는 가장 큰 이슈는 파일 전송이 어렵기때문에 현재는 불가능하다고 공식문서에서 말해줍니다.

그리고 위 부작용 중에 GraphQL로 전환시 클라이언트의 불만이 크기때문에 GraphQL을 사용하는 대기업(Github, Netflix, Facebook 등)에서는 똑같은 기능을 하는 REST, GraphQL API 둘 다 만들고 클라이언트가 선택해서 사용할 수 있게 서비스 하고 있습니다.

그래서 아직 GraphQl로 API개발하는 것은 추천하지 않습니다.

이상으로 오늘 GraphQL 구현 포스팅을 마치겠습니다.

References

https://dgraph.io/learn/courses/datamodel/sql-to-dgraph/develop/graphql-syntax/ https://velog.io/@jeon_131/GraphQL%EA%B3%BC-REST

업데이트: