GraphQL - ORM
목차
GraphQL과 관계형 데이터베이스의 조합이 중요한 이유
1) 데이터 요청 효율성 향상
- GraphQL을 사용하면 클라이언트가 필요한 데이터만 정확히 요청할 수 있어 관계형 데이터베이스에서 과도한 데이터 조회나 여러 번의 API 요청을 방지할 수 있습니다.
- 이는 “오버페칭”과 “언더페칭” 문제를 해결합니다.
2) 스키마 정의와 타입 시스템
- GraphQL의 강력한 타입 시스템은 관계형 데이터베이스의 스키마와 자연스럽게 매핑됩니다.
- 이는 데이터 일관성을 유지하고 개발 시 타입 안전성을 제공합니다.
3) 성능 최적화 가능성
- N+1 문제와 같은 일반적인 GraphQL 성능 이슈는 DataLoader 패턴, 관계형 데이터베이스의 조인 최적화 등을 통해 효과적으로 해결할 수 있습니다.
4) 트랜잭션 지원
- 관계형 데이터베이스의 트랜잭션 기능은 GraphQL 뮤테이션에서 여러 데이터 변경 작업의 원자성을 보장하는 데 필수적입니다.
GrpahQL + ORM 예시
1) Database 연결
1
2
3
4
5
6
from app.config.database_config import POSTGRESQL_USER, POSTGRESQL_PASSWORD, POSTGRESQL_HOST, POSTGRESQL_PORT, POSTGRESQL_DB
from sqlalchemy import create_engine, orm
DATABASE_URL = f"postgresql+psycopg2://{POSTGRESQL_USER}:{POSTGRESQL_PASSWORD}@{POSTGRESQL_HOST}:{POSTGRESQL_PORT}/{POSTGRESQL_DB}"
engine = create_engine(DATABASE_URL, echo=True)
SessionLocal = orm.sessionmaker(bind=engine)
2) ORM을 이용한 Model 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from sqlalchemy import Column, Integer, String, ForeignKey, Text, Table
from sqlalchemy.orm import declarative_base, relationship
from app.database.database import engine
Base = declarative_base()
book_tags = Table("book_tags",
Base.metadata,
Column("book_id", Integer, ForeignKey("books.id")),
Column("tag_id", Integer, ForeignKey("tags.id")))
class PublisherModel(Base):
__tablename__ = "publishers"
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
location = Column(String)
published_year = Column(Integer)
books = relationship("BookModel", back_populates="publishers")
class BookModel(Base):
__tablename__ = "books"
id = Column(Integer, primary_key=True, index=True)
title = Column(String)
author = Column(String)
publisher_id = Column(Integer, ForeignKey("publishers.id"))
publishers = relationship("PublisherModel", back_populates="books")
reviews = relationship("ReviewModel", back_populates="books")
tags = relationship("TagModel", secondary=book_tags, back_populates="books")
class ReviewModel(Base):
__tablename__ = "reviews"
id = Column(Integer, primary_key=True, index=True)
content = Column(Text)
rating = Column(Integer)
book_id = Column(Integer, ForeignKey("books.id"))
books = relationship("BookModel", back_populates="reviews")
class TagModel(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
books = relationship("BookModel", secondary=book_tags, back_populates="tags")
# 테이블이 없으면 생성
Base.metadata.create_all(bind=engine)
3) graphQL 스키마 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@strawberry.type
class Review:
id: int
content: str
rating: int
book_id: int
@strawberry.type
class Tag:
id: int
name: str
@strawberry.type
class Book:
id: int
title: str
author: str
publisher_id: int
@strawberry.field
async def reviews(self) -> List[Review]:
try:
db = SessionLocal()
db_reviews = db.query(ReviewModel).filter(ReviewModel.book_id == self.id).all()
return [Review(id=r.id, content=r.content, rating=r.rating, book_id=r.book_id) for r in db_reviews]
finally:
db.close()
@strawberry.field
async def tags(self) -> List[Tag]:
try:
db = SessionLocal()
book = db.query(BookModel).filter(BookModel.id == self.id).first()
return [Tag(id=tag.id, name=tag.name) for tag in book.tags] if book else []
finally:
db.close()
@strawberry.type
class Publisher:
id: int
name: str
location: str
published_year: int
@strawberry.field
async def books(self) -> List[Book]:
try:
db = SessionLocal()
db_books = db.query(BookModel).filter(BookModel.publisher_id == self.id).all()
return [Book(id=b.id, title=b.title, author=b.author, publisher_id=b.publisher_id) for b in db_books]
finally:
db.close()
4) Query 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@strawberry.type
class Query:
@strawberry.field
async def books(self) -> List[Book]:
db = SessionLocal()
books = db.query(BookModel).all()
db.close()
return [Book(id=b.id, title=b.title, author=b.author, publisher_id=b.publisher_id) for b in books]
@strawberry.field
async def book(self, title: str) -> Optional[Book]:
db = SessionLocal()
find_book = db.query(BookModel).filter(BookModel.title == title).first()
db.close()
if find_book:
return Book(id=find_book.id, title=find_book.title, author=find_book.author, publisher_id=find_book.publisher_id)
return None
5) Mutation 작성 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@strawberry.type
class Mutation:
@strawberry.mutation
async def create_book(self, book_input: BookInput) -> Book:
db = SessionLocal()
publisher_model = db.query(PublisherModel).filter(PublisherModel.id == book_input.publisher_id).first()
if not publisher_model:
db.close()
raise ValueError("유효하지 않은 출판사 ID입니다.")
new_book = BookModel(title=book_input.title,
author=book_input.author,
publisher_id=book_input.publisher_id)
db.add(new_book)
db.commit()
db.refresh(new_book)
new_book = Book(id=new_book.id,
title=new_book.title,
author=new_book.author,
publisher_id=new_book.publisher_id)
await change_event_queue.put(ChangeEvent(entity_type=EntityType.BOOK,
event_type=EventType.CREATE,
book=new_book))
db.close()
return new_book
@strawberry.mutation
async def update_book(self, id: int, update: BookUpdate) -> Optional[Book]:
db = SessionLocal()
book_model = db.query(BookModel).filter(BookModel.id == id).first()
if not book_model:
db.close()
return None
if update.title is not None:
book_model.title = update.title
if update.author is not None:
book_model.author = update.author
if update.publisher_id is not None:
publisher_model = db.query(PublisherModel).filter(PublisherModel.id == update.publisher_id).first()
if not publisher_model:
db.close()
raise ValueError("유효하지 않은 출판사 ID입니다.")
book_model.publisher_id = update.publisher_id
db.commit()
db.refresh(book_model)
update_book = Book(id=book_model.id,
title=book_model.title,
author=book_model.author,
publisher_id=book_model.publisher_id)
await change_event_queue.put(ChangeEvent(entity_type=EntityType.BOOK,
event_type=EventType.UPDATE,
book=update_book))
db.close()
return update_book
다음글에는 만들어진 GraphQL 서버에 Query 하는 방법에 대해서 자세히 설명하겠습니다.