Operation/GraphQL

GraphQL 이해하기!

JaeHoney 2023. 7. 2. 20:39

GraphQL 이란 2012년 페이스북의 클라이언트 데이터 전송 방식 개선 목적으로 시작되었다.

GraphQL이란 클라이언트와 서버의 통신 명세이다. (REST와 마찬가지로 실체는 없다.)

  • 명세를 기반으로 다양한 라이브러리가 생성되었다.

구조(스키마)와 행동(리졸버)로 구성된다.

vs REST API

GraphQL과 REST API의 차이를 알아보자.

GraphQL REST
구성 스키마 / 타입 시스템 URL endpoints
동작 Query, Mutation, Subscription CRUD
End-point 단일 접점(API 1개) URL 집합
데이터 포맷 Only JSON JSON, XML, …
관점 클라이언트 주도 설계 서버 주도 설계
러닝 커브 어려움 보통 (비교적 쉬움)

그러면 이러한 차이는 어떤 장점이 있을까?

  • 클라이언트에게 많은 제어권을 넘길 수 있다. (필요한 필드 목록 등을 클라이언트에서 주도할 수 있다.)
  • 동일한 스펙(Query)으로 여러 서버에 질의할 수 있다.
  • 오버페칭, 언더페칭이 적다.
  • UI에 의존적이지 않고, 도메인 중심적으로 설계가 가능하다.
  • 상대적으로 빠르다.

이제 위 특징들을 하나씩 살펴보자.

클라이언트 주도 설계

클라이언트 측은 사용자의 이름을 다음과 같이 질의할 수 있다.

  • request:{ hero { name } }
  • response: { "hero": { "name": "Luke Skywalker" } }

이때 이름 뿐만 아니라 키와 머리색도 질의하고 싶다면 다음과 같이 질의할 수 있다.

  • request: { hero { name height hairColors } }
  • response: { "hero": { "name": "Luke Skywalker", "height": 1.72, "hairColors": ["black", "brown"] } }

즉, 클라이언트에서 필요한 필드를 주도적으로 조회할 수 있게 된다.

단일 접점

GraphQL은 API end-point가 하나라서 클라이언트 입장에서 사용하기 편리하다.

오버페칭, 언더페칭

만약 사용자 한명의 많은 필드 중에서 주소만 조회하고 싶은 경우가 있다.

REST의 경우 GET /사용자/id/주소가 존재하지 않는다면 GET /사용자로 조회를 해야할 것이다. 이때 오버페칭이 발생한다.

반대로 클라이언트의 처리에 필요한 데이터가 부족한 경우도 종종생겨서, 추가 작업을 해야하는 경우가 생긴다. 즉, 언더페칭이 발생한다.

GraphQL을 사용하면 이를 해결할 수 있다.

단, 이러한 요구사항에 맞게 스키마 설계가 되어야 하고 리졸버를 잘 구현하는 것이 필요하다.

GraphQL 단점

GraphQL도 당연히 장점만 존재하지는 않는다.

  • GraphQL 스키마의 유지보수가 필요하다.
    • 스키마 설계
    • 트리 그래프의 깊이 제어
  • 캐싱 전략을 적용하기 어렵다. (쿼리에 따라 응답이 매우 동적이기 때문)
  • 학습 곡선이 크다.

문서화

REST API의 경우 Swagger 도구 등을 활용해서 API 스펙을 문서화해야 한다.

GraphQL은 스키마 자체가 API 문서이기 때문에 별도로 문서화를 할 필요가 없다.

개발 방식

GraphQL 개발 방식은 아래의 두 가지 방식이 있다.

  • Schema First - 스키마를 먼저 정의 -> 코드 작성
  • Code First - 코드 작성 -> 스키마 자동 생성

일반적으로는 클라이언트 주도적으로 설계할 수 있고, 추후 변경 비용이 적은 SchemaFirst 방식을 권장한다.

자바 프레임워크

JVM & 스프링 기반의 서버에서는 아래의 도구를 활용할 수 있다.

  • Spring for GraphQL
  • Netflix DGS
  • GraphQL Kick Starter

GraphQL 쿼리어

GraphQL의 쿼리어는 문법에 따라 3가지로 나뉜다.

  • query - 조회
  • mutation - 입력, 수정, 삭제
  • subscription - 구독

1. Query

Query는 조회, 질의를 할 때 사용한다.

스키마에 아래와 같이 쿼리 타입을 정의할 수 있다.

type Query {
    shows: [Show]
}

type Show {
    title: String,
    actors: [Actor]
}

여기서 Key는 클라이언트 요청에 의해 질의할 데이터이고, Value는 응답이 된다.

2. Mutation

Mutation은 생성, 수정, 삭제 시 사용한다.

type Mutation {
    addRating(title: String, stars: Int):Rating 
}

type Rating {
    avgStars: Float
}

Query와 마찬가지로 응답이 Value에 해당한다.

3. Subscription

Subscription은 클라이언트 측에서 서버의 이벤트를 구독하고 처리할 때 사용한다.

type Subscription {
    commentAdded(postId: ID!):Comment 
}

GraphQL 필드

1. 타입

GraphQL은 아래 타입을 지원합니다.

  • Scalar(Int, Float, String, Boolean, ID, Custom ..)
  • Object
  • Input
  • Enum
  • Interface
  • Union
  • Fragment

아래는 Custom Scalar의 경우 아래와 같이 표현할 수 있다.

type Query {
    products(page: Int, size: Int): [Product]
}

type Product {
    productId: ID
    name: String
    price: BigDecimal
    vendorId: Int
    status: ProductStatus
    imageUrl: String
    imageDetailUrl: String
    productDesc: String
    isExposed: Boolean
    isDeleted: Boolean
    createdAt: DateTime
    createdBy: String
    updatedAt: DateTime
    updatedBy: String
}

enum ProductStatus {
    READY_TO_SELL
}

scalar BigDecimal
scalar DateTime

BigDecimal과 DateTime과 같은 커스텀 스칼라를 선언하고, 타입으로 사용할 수 있다.

2. Null, 배열 표현

필드(파라미터, 응답 값)를 표현할 때는 크게 보면 두 가지만 알면 된다.

  • null 규칙을 !로 표현한다.
  • 배열을 표현할 때는 []를 사용한다.

null 규칙의 경우 아래를 참고하자.

Expression Description
String 널을 허용한다.
String! 널을 허용하지 않는다.
[String!]! 리스트와 요소 모두 널을 허용하지 않는다.

예를 들어 Product 질의 시 id에 null을 넣을 수 있고, null이 반환될 수도 있으면 아래와 같이 작성한다.

type Query {
    Product(id: ID!): Product!    
}

3. 기본값 표현

기본 값의 경우 아래와 같이 표현할 수 있다.

type Query {
    products(pages: Int=1 size: Int=10): [Product!]!
}

GraphQL 주의사항

GraphQL을 사용할 때 고려할 점은 다음과 같다.

  • 데이터 양의 제어
  • 깊이 제어
  • 쿼리 복잡도 제어
  • 클라이언트 사이드의 커넥션 타임아웃

REST API도 마찬가지지만, 너무 많은 데이터를 한 번에 요청하면 서버가 죽을 수 있다.

그리고 트리 형태의 데이터를 내려주므로 부하와 사용성의 Trade off 발생한다.

결과적으로 사용자(클라이언트)의 사용성과 서버 부하를 적절히 조율하고 제어하는 것이 필요하다.

참고