20170213

GraphQL 처음 도입해본 팀

내부 어드민 API 하나를 GraphQL로 재작성. Python쪽은 Graphene 2.0beta 써봤다. 팀이 처음 도입하는 상황이라 적어두기.

왜 도입했나: 모바일/웹에서 같은 엔드포인트 쓰는데 필드 요구가 달라서 REST 엔드포인트가 자꾸 늘어남. 캐시 문제랑 overfetch 이슈가 누적.

스키마 예시 (Graphene):

import graphene
from graphene_django import DjangoObjectType

class UserType(DjangoObjectType):
    class Meta:
        model = User
        fields = ('id', 'email', 'name')

class Query(graphene.ObjectType):
    user = graphene.Field(UserType, id=graphene.Int(required=True))
    users = graphene.List(UserType)

    def resolve_user(self, info, id):
        return User.objects.get(pk=id)

    def resolve_users(self, info):
        return User.objects.all()

schema = graphene.Schema(query=Query)

클라이언트는 필요한 필드만 명시:

{ user(id: 1) { email name } }

좋았던 점:

  • 프론트에서 새 필드 필요할 때 백엔드 변경 없이 쿼리만 수정 → 진짜 편함
  • 스키마가 문서 역할. GraphiQL IDE가 자동 생성
  • 타입 명세가 강제되니까 팀원끼리 대화 혼란 줄어듦

걸린 점들 (조심):

1. N+1 쿼리. 리스트 쿼리에서 각 item의 related 객체 resolver 돌리면 ORM 호출이 행 수만큼 나감. DataLoader 같은 배치 로더 꼭 필요. 이거 안 쓰면 DB 폭주.

2. 권한 체크. REST는 URL 단위로 걸면 됐는데 GraphQL은 필드 단위로 걸어야 함. resolve_ 안에서 info.context.user 체크. 이거 빠뜨리면 사고남. 공통 데코레이터로 묶어서 관리.

3. 쿼리 복잡도 제한. 악의적인 중첩 쿼리 막아야 함. depth limit / cost analysis 플러그인 필요. { user { friends { friends { friends ... } } } } 같은거 방어.

4. HTTP 캐시. GET이 아니라 POST 위주라 브라우저/CDN 캐시 활용 어려움. persisted query 쓰거나 클라이언트(apollo 등)에 캐시 위임.

어드민은 괜찮은데 공개 API까지 갈지는 판단 보류. 트래픽 큰 엔드포인트는 REST가 성능/캐시 면에서 여전히 유리함. 하이브리드로 가는게 현실적인듯.