GraphQL 예제 구현
이번 포스팅에서는 이전에 설명했던 GraphQL을 실제로 구현해보는 포스팅을 하겠습니다.
만약 GraphQL의 개념이 궁금하신 분은 아래 링크를 눌러서 확인하시면 됩니다.
Link: GraphQL의 개념과 장단점
GraphQL 구조
아래 이미지를 통해 GraphQL의 구조를 확인할 수 있습니다.
- Resolver : 데이터 엑세스 및 조작을 위한 임의의 함수
- Ariadne : GraphQL 쿼리언어를 구체화해주는 파이썬 라이브러리
클라이언트가 GraphQL쿼리를 통해서 데이터를 POST로 요청하면 API에서 쿼리를 받습니다. 그리고 그 쿼리를 Ariadne라는 라이브러리로 컴파일한 후 Resolver로 보내줍니다. Resolver는 DB에 접근해서 클라이언트가 요청한 데이터를 가져와서 return 해주는 구조로 되어있습니다.
GraphQL의 조회, 생성, 삭제, 수정 등 모든 요청은 POST로 처리합니다. 그 이유는 아래에서 설명하겠습니다.
GraphQL 쿼리
GraphQL API를 만들어보기에 앞서 GraphQL 쿼리에 대해서 알아보겠습니다.
GraphQL은 SQL을 바탕으로 만들어진 쿼리언어이기 때문에 SQL과 비교학습을 통해 보다 쉽게 이해할 수 있습니다.
위 그림처럼 원하는 테이블을 설정하고 가져올 데이터를 GraphQL쿼리로 작성하면 됩니다.
GraphQL이 POST 요청만 하는 이유
REST API에서는 수정, 조회가 필요할 때 여러번 요청해야 합니다. 하지만 GraphQL의 경우 한번의 쿼리요청으로 처리가 가능합니다.(아래그림참조)
UPDATE, SELECT 3번의 요청을 한번에 처리 이렇게 여러 요청을 한번에 처리할 수 있는 장점때문에 일반적으로 POST요청만 사용합니다.
GraphQL 구현
GraphQL 자체는 쿼리 언어이기 때문에 구체화 할 수 있는 언어와 라이브러리가 필요(아래 주소 참조)
GraphQL 공식홈 Link: GraphQL언어, 라이브러리
- GraphQL 개발 언어로 Javascript를 많이 사용하지만 필자는 python을 사용애서 개발했습니다.
- Python라이브러리로 “Ariadne”사용
GraphQL 구현 예시
- REST API로 구현된 모델 조회기능(model, model_vsn)을 GraphQL로 구현해 보겠습니다.
- DB는 Postgrasql의 Alertsys 로컬 DB사용
GraphQL 조회기능 구현
- /model/model_id
- /model/model_id/version
1. 설치
pip install flask ariadne flask-sqlalchemy flask-cors
2. Flask app 생성
아래 그림처러 디렉토리 구조를 만들었습니다.
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
Flask run 코드로 실행
http://127.0.0.1:5000/graphql
위 주소로 입력했을때 아래 그림처럼 나오면 Flask 정상 작동
위 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정보 가져올수 있습니다.
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정보를 가져올수 있습니다.
결론
위 코드로 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