[FastAPI] 3. SQLAlchemy + Graphene ์กฐํ•ฉ์œผ๋กœ GraphQL ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

๋ฐ˜์‘ํ˜•

์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” GraphQL์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

 

 

What is GraphQL ?

GraphQL์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ๋Š” ๊ทธ๋ฆฌ ๊ธธ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๊ธฐ์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋‹ค๋ค„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

GraphQL์€ Facebook์—์„œ ๊ฐœ๋ฐœํ•œ ๋ฐ์ดํ„ฐ ํ‘œํ˜„ ์–ธ์–ด๋กœ ์—ฌ๊ธฐ์„œ QL์ด Query Language์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด REST API๋ฅผ ์‚ฌ์šฉํ•˜์˜€๊ณ , REST API๋Š” ๋ฉ”์†Œ๋“œ์™€ ์š”์ฒญ ๋ฐ์ดํ„ฐ ํ˜น์€ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ ์žฌํ•˜๋Š” ๋“ฑ์„ ์ˆ˜ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿฌ๋‚˜ GraphQL์€ ๋ฉ”์†Œ๋“œ ์—†์ด ์˜ค์ง Query Language๋งŒ์„ ์ด์šฉํ•˜์—ฌ ์›ํ•˜๋Š” ๋ชจ๋ธ์„ ์ ์žฌํ•˜๊ณ , ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์›ํ•˜๋Š” ํ•ญ๋ชฉ์„ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ง์ ‘ ๋ช…์‹œํ•จ์œผ๋กœ์จ ์„œ๋ฒ„์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” ๋ฐ์ดํ„ฐ๋งŒ์„ ์˜์กดํ•˜์ง€ ์•Š๊ณ  ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์ด ์ƒ๊ธฐ๋ฉฐ ์ด ์žฅ์ ์€ ์ฐจํ›„ ๋„คํŠธ์›Œํฌ ๋Œ€์—ญํญ์„ ์ ˆ์•ฝ์‹œํ‚ค๋Š” ๋ฐ ๋„์›€์„ ์ฃผ๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

 

์ด ์™ธ์—๋„ ๋‹ค์–‘ํ•œ ์žฅ์ ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

 

  • UnderFetching

    HTTP ์š”์ฒญ 1๋ฒˆ์— ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Œ.

  • OverFetching

    ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์›ํ•˜๋Š” ํ•ญ๋ชฉ๋งŒ ๋ช…์‹œํ•˜๋ฉด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋งŒ์„ ๋ฐ˜ํ™˜ (๋Œ€์—ญํญ ์ ˆ์•ฝ, ๊ทธ๋Ÿฌ๋‚˜ ์•ฝ๊ฐ„์˜ ์˜ค๋ฒ„ํ—ค๋“œ ๋ฐœ์ƒ)

  • Type Specification

    ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•ด ์ž๋ฃŒํ˜•์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์„ ํ•„์ˆ˜๋กœ ํ•˜๋ฉฐ ์ด๋กœ ์ธํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Œ.

  • Cross Platform

    Web, Mobile์„ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๊ณ , ํ•˜๋‚˜์˜ API๋กœ ๋ชจ๋“  ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•จ.

ํƒ€์ž…์„ ๋ช…์‹œํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด์„œ๋Š” ์กฐ๊ธˆ ์˜์•„ํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ Python์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ DTO๋ฅผ ๊ฐ–์ถ”์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ DTO๋ฅผ ๊ฐ–์ถ”์ง€ ์•Š์œผ๋ฉด ๊ฐ API๋ณ„๋กœ ์–ด๋–ป๊ฒŒ ์š”์ฒญ์„ ํ•ด์•ผํ• ์ง€, ์‘๋‹ต์„ ์ฃผ๋Š”์ง€๊ฐ€ ๋ช…์‹œ๋˜์–ด ์žˆ์ง€ ์•Š์•„ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ๋žŒ ์ž…์žฅ์—์„œ๋Š” ์˜คํžˆ๋ ค ํ˜ผ๋ž€์„ ์•ผ๊ธฐํ•  ์ˆ˜๋„ ์žˆ๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. 

 

๊ทธ๋Ÿฌ๋‚˜ GraphQL์—์„œ๋Š” ์ด๋Ÿฌํ•œ ํƒ€์ž… ์ •์˜๊ฐ€ ๊ธฐ๋ณธ์ด๋ฉฐ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์˜ ์ž๋ฃŒํ˜•์ด ๋‹ค๋ฅผ ๊ฒฝ์šฐ 422 ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. 

 

 

 

 

Graphene

Graphene๋Š” Python์—์„œ GraphQL์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ์•„๋งˆ Node.js์—์„œ GraphQL์„ ์จ๋ณด์‹  ๋ถ„๋“ค์€ Apollo ๋ผ๋Š” ๊ฒƒ์„ ๋“ค์–ด๋ณด์…จ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. Python์—์„œ ์ด์™€ ๋น„์Šทํ•œ Ariadne๋Š” ์Šคํ‚ค๋งˆ ์šฐ์„  ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ Python ์ฝ”๋“œ๊ฐ€ ์•„๋‹Œ SDL(Schema Definition Language)์— ์˜์กดํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜์ ์œผ๋กœ๋Š” ์ ‘๊ทผํ•˜๊ธฐ ์–ด๋ ค์šด ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿฌ๋‚˜ Graphene๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ฝ”๋“œ ์šฐ์„  ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ GraphQL์„ ์ฒ˜์Œ ์‚ฌ์šฉํ•ด๋ณด์‹œ๋Š” ๋ถ„๋“ค์—๊ฒŒ ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. Schema์— ๋Œ€ํ•œ ๊ฐœ๋…์ด๋‚˜ ์ฝ”๋“œ๋ฅผ ์ž˜ ๋ชฐ๋ผ๋„ Python ์ฝ”๋“œ๋งŒ ์งค ์ค„ ์•ˆ๋‹ค๋ฉด GraphQL์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ๊ฑฐ๊ธฐ์— SQLAlchemy๋ผ๋Š” ORM๊ณผ๋„ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์–ด, ๋งค์šฐ ๋น ๋ฅธ ๊ฐœ๋ฐœ์— ์šฉ์ดํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

 

 

Graphene-Python

PS. Your API is a User Interface Simple yet Powerful Graphene-Python is a library for building GraphQL APIs in Python easily, its main goal is to provide a simple but extendable API for making developers' lives easier. But, what is GraphQL? GraphQL is a da

graphene-python.org

Graphene๋Š” ๋ณด๋‹ค์‹œํ”ผ ๋‹จ๋…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๊ณ , Flask์™€ ํ†ตํ•ฉํ•˜๊ฑฐ๋‚˜, SQLAlchemy์™€ ํ†ตํ•ฉํ•˜๋Š” ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ด ์ค‘์—์„œ๋„ ์ด ํฌ์ŠคํŠธ์—์„œ๋Š” SQLAlchemy์™€ ํ†ตํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

 

 

SQLAlchemy + Graphene

์ง€๋‚œ ํฌ์ŠคํŠธ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ ํ”„๋กœ์ ํŠธ์™€ ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์•„๋ž˜์ฒ˜๋Ÿผ graphene-sqlalchemy ๋””ํŽœ๋˜์‹œ๋งŒ ์ถ”๊ฐ€ํ•ด์ฃผ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

[tool.poetry]
name = "fastapiexample"
version = "0.1.0"
description = ""
authors = ["Neon K.I.D <contact@neonkid.xyz>"]

[tool.poetry.dependencies]
python = "^3.8.5"
fastapi = "^0.63.0"
uvicorn = "^0.13.2"
SQLAlchemy = "^1.3.22"
psycopg2-binary = "^2.8.6"
graphene-sqlalchemy = "^2.3.0"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

๊ธฐ์กด์˜ ์ฝ”๋“œ๋ฅผ ์ด์–ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— REST API ์ฝ”๋“œ์™€ ๊ฒน์น  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค. ์ด ํฌ์ŠคํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ผํ•œ๋‹ค๋ฉด GraphQL๊ณผ REST API๋ฅผ ๋™์‹œ์— ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์šฐ๋ฆฌ๋Š” ์ง€๋‚œ ์‹œ๊ฐ„์— ๊ฐ„๋‹จํ•œ ๋ฉ”๋ชจ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” DB ์Šคํ‚ค๋งˆ๋ฅผ ๋งŒ๋“ค์—ˆ๊ณ , ์—ฌ๊ธฐ์— ๋ฉ”๋ชจ์˜ ์ œ๋ชฉ๊ณผ ์ปจํ…์ธ , ์ฆ๊ฒจ์ฐพ๊ธฐ ์—ฌ๋ถ€ ๋“ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

์ด๋ฅผ ํ† ๋Œ€๋กœ GraphQL ๊ธฐ๋ฐ˜์˜ API๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

 

Resolver

ObjectType์€ graphene์—์„œ GraphQL์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ์ ์ธ Query ํด๋ž˜์Šค๋กœ GraphQL API๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ ๋งŒ๋“ค์–ด์•ผ ํ•  ํ•„์ˆ˜ ์š”์†Œ์ž…๋‹ˆ๋‹ค.

from graphene import ObjectType


class Query(ObjectType):
	

relay๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DB ์„œ๋ฒ„์™€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from graphene import ObjectType, relay


class Query(ObjectType):
    node = relay.Node.Field()

๋จผ์ € Query์— node๋ผ๋Š” ๋ณ€์ˆ˜๋ฅผ ๋‹ด๊ณ , relay ํ•„๋“œ์ž„์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•ž์œผ๋กœ GraphQL์—์„œ node ๋ฐ‘์œผ๋กœ DB์—์„œ ๋ถˆ๋Ÿฌ์˜จ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์ด DB์™€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ์ปค๋„ฅ์…˜์„ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

from graphene import relay
from graphene_sqlalchemy import SQLAlchemyObjectType


class MemoModel(SQLAlchemyObjectType):
    class Meta:
        model = Memo
        interfaces = (relay.Node,)


class MemoConnection(relay.Connection):
    class Meta:
        node = MemoModel

relay์— ์žˆ๋Š” Connection์„ ์ƒ์†๋ฐ›๊ณ , ์ด์— ํ•ด๋‹นํ•˜๋Š” ๋ชจ๋ธ์„ node์— ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

from graphene import ObjectType, relay, String
from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField


class Query(ObjectType):
    node = relay.Node.Field()

    memo = SQLAlchemyConnectionField(MemoConnection, id=String())
    memo_list = SQLAlchemyConnectionField(MemoConnection, sort=MemoModel.sort_argument())

    def resolve_memo(self, info, **kwargs):
        id = kwargs.get('id')

        memos_query = MemoModel.get_query(info)

        if id is not None:
            return memos_query.filter_by(id=id)

    def resolve_memo_list(self, info, **kwargs):
        return MemoModel.get_query(info).all()

๋งˆ์ง€๋ง‰์œผ๋กœ ์•„๊นŒ ์œ„์—์„œ ๋งŒ๋“  Query์— memo์™€ memo_list์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค. memo๋Š” ๋‹จ์ผ ๋ฉ”๋ชจ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ์ด๊ณ , memo_list๋Š” ๋ณต์ˆ˜ ๊ฐœ์˜ ๋ฉ”๋ชจ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฐ ID๋กœ ์กฐํšŒ๋ฅผ ํ•˜๋Š” REST API๋ฅผ ๋งŒ๋“ค์—ˆ๊ธฐ ๋•Œ๋ฌธ์— id ๊ฒ€์ƒ‰์„ ์œ„ํ•œ id ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์ „์ฒด ๋ฉ”๋ชจ ์กฐํšŒ์‹œ ์ •๋ ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด memo_list์—๋Š” sort ์•„๊ทœ๋จผํŠธ๋ฅผ ํ•˜๋‚˜ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

from graphene import Schema
from starlette.graphql import GraphQLApp


app.add_route('/graphql', GraphQLApp(schema=Schema(query=Query)))

์ด์ œ /graphql ์ด๋ผ๋Š” ์—”๋“œํฌ์ธํŠธ์— GraphQL API๋ฅผ ๋ฌถ์–ด์ฃผ๋ฉด graphql ์—”๋“œํฌ์ธํŠธ ํ•˜๋‚˜๋กœ ๋ฐ์ดํ„ฐ๋ฅผ READ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

 

 

Mutation

Mutation์€ GraphQL์—์„œ Collection์„ ์ƒˆ๋กœ ๋งŒ๋“ค๊ณ , ์ˆ˜์ •ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ObjectType์ž…๋‹ˆ๋‹ค. graphene์—์„œ๋„ GraphQL๋กœ ๋ฐ์ดํ„ฐ๋ฅผ INSERT, UPDATEํ•  ๋•Œ ์ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

from graphene import String, Mutation, Boolean


class InsertMemo(Mutation):
    class Arguments:
        title = String(required=True)
        content = String()
        is_favorite = Boolean()
        
    memo = Field(lambda: MemoModel)
    
    def mutate(self, info, **kwargs):
        memo = Memo(**kwargs)
        
        db_session.add(memo)
        db_session.commit()
        
        return InsertMemo(memo)

๋จผ์ € ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฉ”๋ชจ๋ฅผ ์‚ฝ์ž…ํ•  ๋•Œ๋Š” Mutation์„ ์ƒ์† ๋ฐ›๊ณ , ๊ทธ์— ์‚ฌ์šฉํ•  Argument๋ฅผ ์ •์˜ํ•ด์ค๋‹ˆ๋‹ค. ํƒ€์ž…์€ graphene ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณต๋˜๋Š” ๊ฒƒ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  SQLAlchemy์˜ ์„ธ์…˜์„ ์ด์šฉํ•˜์—ฌ DB์— ์˜์†ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

from graphene import String, Mutation, Boolean, Field


class UpdateMemo(Mutation):
    class Arguments:
        id = String(required=True)
        title = String()
        content = String()
        is_favorite = Boolean()

    memo = Field(lambda: MemoModel)

    def mutate(self, info, **kwargs):
        memo = db_session.query(Memo).filter_by(id=kwargs.get('id')).first()

        for key, value in kwargs.items():
            setattr(memo, key, value)

        db_session.commit()
        
        return UpdateMemo(memo)

๋น„์Šทํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฉ”๋ชจ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ๋•Œ ์œ„์™€ ๊ฐ™์ด ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

from graphene import String, Mutation, Boolean


class DeleteMemo(Mutation):
    class Arguments:
        id = String(required=True)
    
    def mutate(self, info, id):
        memo = db_session.query(Memo).filter_by(id=id)
        db_session.delete(memo)
        return ""

์‚ญ์ œ๋ฅผ ํ•  ๋•Œ๋„ ๋™์ผํ•˜๊ฒŒ ๊ฐœ๋ฐœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

from graphene import ObjectType


class Mutation(ObjectType):
    create_memo = InsertMemo.Field()
    update_memo = UpdateMemo.Field()
    delete_memo = DeleteMemo.Field()

GraphQL App ํด๋ž˜์Šค์— ๋‹ด์„ Mutation ObjectType์„ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์ฃผ๊ณ , ์•„๊ทœ๋จผํŠธ์— ๊ฐ๊ฐ ๋งŒ๋“ค์–ด์ค€ API๋ฅผ ๋„ฃ์Šต๋‹ˆ๋‹ค.

from graphene import Schema
from starlette.graphql import GraphQLApp


app.add_route('/graphql', GraphQLApp(schema=Schema(query=Query, mutation=Mutation, types=[MemoModel])))

๋งˆ์ง€๋ง‰์œผ๋กœ GraphQLApp ํด๋ž˜์Šค์— Mutation๊ณผ ์‚ฌ์šฉํ•  Type์„ ๋„ฃ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉํ•  Type์„ ๋„ฃ์„ ๋•Œ๋Š” grpahene ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์žˆ๋Š” ํด๋ž˜์Šค๋กœ ์ƒ์† ๋ฐ›์€ ๊ฒƒ์„ ๋„ฃ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

 

 

 

 

Test

ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด์„œ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ Resolver์™€ Mutation๋งŒ์„ ๊ฐ€์ง€๊ณ  ์ง„ํ–‰ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ์—ญ์‹œ Postman์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

mutation {
	createMemo(title: "", content: "") {
		memo {
			id
			title
			content
		}
	}
}

Mutation์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. GraphQL์„ ์‚ฌ์šฉํ•  ๋•Œ mutation์ž„์„ ์„ ์–ธํ•˜๊ณ , ์ฝ”๋“œ์—์„œ ์ •์˜ํ•œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฐ˜ํ™˜ํ•  ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๋ถ€๋ฅธ ํ›„ ๋ฐ˜ํ™˜ํ•˜๊ณ ์ž ํ•˜๋Š” ํƒ€์ž…์„ ๋ช…์‹œํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

query {
	memoList {
		edges {
			node {
				id
				title
				content
			}
		}
	}
}

๋ฐ˜๋Œ€๋กœ DB์— ์ ์žฌ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ ์ž ํ•  ๋•Œ๋Š” query๋ฅผ ์„ ์–ธํ•ด์ค๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฉ”๋ชจ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” memoList๋ฅผ ์ •์˜ํ•˜๊ณ  ๊ทธ์— ํ•ด๋‹นํ•˜๋Š” ํ•˜์œ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€๋ฅด๊ธฐ ์œ„ํ•ด edges์™€ node๋ฅผ ์ž…๋ ฅํ•œ ๋‹ค์Œ ์›ํ•˜๋Š” ์•„๊ทœ๋จผํŠธ๋ฅผ ๋‚ด๋ฆฌ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

 

 

 

๋งˆ์น˜๋ฉฐ...

์—ฌ๊ธฐ๊นŒ์ง€ FastAPI + SQLAlchemy + Grpahene ์กฐํ•ฉ์œผ๋กœ ๊ฐ„๋‹จํ•œ GraphQL API๋ฅผ ๋งŒ๋“œ๋Š” ๋ฒ•๊นŒ์ง€ ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค. GraphQL์„ ์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์žฅ๋‹จ๊ณผ ์ฐจ์ด์ ๋งŒ ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•˜๊ณ  ๋„˜์–ด๊ฐ”๊ธฐ ๋•Œ๋ฌธ์— ์ดˆ๋ฉด์— ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์–ด๋ ค์šธ ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿฌ๋‚˜ ๋ณด๋‹ค์‹œํ”ผ REST API์— ๋น„ํ•ด API ๊ตฌ์กฐ๊ฐ€ ๊ฐ„๋‹จํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฝ‘์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์€ ๋Œ€์—ญํญ์„ ์ค„์ด๊ณ , ํ˜ธ์ถœ ํšŸ์ˆ˜๊ฐ€ ๋‚ฎ์•„์ง„๋‹ค๋Š” ์ ์— ์žˆ์–ด ๋งค์šฐ ํฐ ์ด๋“์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

FastAPI์—์„œ ๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ REST API ๊ทธ๋Œ€๋กœ GraphQLApp ์ธ์Šคํ„ด์Šค๋งŒ์„ ๋งŒ๋“ค์–ด GraphQL API๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด์— REST API๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ GraphQL๋กœ ์ „ํ™˜์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜์‘ํ˜•
TAGS.

Tistory Comments