ABCD의 GraphQL 첫 날 01

GraphQL Study01

GraphQL이란?

  • API를 만들 때 사용할 수 있는 쿼리 언어

  • 타입 시스템을 사용하여 쿼리를 실행하는 서버사이드 런타임

    런타임(runtime) : 컴퓨터 프로그램이 실행되고 있는 동안의 동작

    • 쿼리를 실행하고 이에 대한에 데이터를 받을 수 있는 런타임
  • GraphQL은 선언형(declarative) 데이터 패칭(fetching) 언어

  • 특정 언어, 프레임워크와 무관

특징

  • 위계적
    • 필드 내에 필드가 중첩 가능
    • 쿼리와 그에 대한 반환 데이터는 형태가 서로 같다.
  • 제품 중심적
    • 클라이언트가 요구하는 데이터와 클라이언트가 지원하는 언어 및 런타임에 맞춰 동작
  • 엄격한 타입 제한
    • GraphQL 서버는 GraphQL 타입 시스템을 사용
    • 스키마 데이터 포인트마다 특정 타입을 명시, 이를 기초로 유효성 검사
  • 클라이언트 맞춤 쿼리
    • GraphQL 서버는 클라이언트 쪽에서 받아서 사용 할 수 있는 데이터를 제공
  • 인트로스펙티브(introspective)
    • 스키마 확인 가능

기존 REST의 문제

  • 오버패칭

    • 클라이언트 측에서 필요하지 않은 데이터까지 수신하는 경우가 다수

    ex. 현재 페이체크 측 관리자 페이지에서 회사 이름만을 받아오고 싶은데, 회사 전체 정보를 수신하고 있음

  • 언더패칭

    • 필요한 데이터를 요청하기 위해 서브데이터를 추가적으로 요청해야 하는 경우, 서버에 2번의 요청 필요
  • 유연성 부족

    • 하나의 기능을 만들 때마다 엔드포인트가 늘어나는 구조

쿼리어

  • query

    • select
  • mutation

    • insert, update, delete
  • subscription

    • 데이터에 변화가 생기면 알림을 주는 형태

query

실습 가능 링크

1
2
3
4
5
query {
hero {
name
}
}

query 키워드는 생략 가능

위의 코드는 아래와 같다.

1
2
3
4
5
6
> {
> hero {
> name
> }
> }
>

위의 요청을 해석하면, hero란 이름으로 정의된 타입에서 name만 가지고 와달라, 가 되며 서버에서 제대로 정의가 되어 있다면 아래가 응답이 된다.

1
2
3
4
5
6
7
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}

응답은 항상 data라는 필드 안에 존재한다.

여기서 눈여겨 보아야 할 것은 쿼리와 결과가 정확히 동일하다는 점

GraphQL 쿼리는 연관된 객체와 필드를 탐색할 수 있기에, 기존 REST언더패칭의 문제 해결 가능

  • 요청

    1
    2
    3
    4
    5
    6
    7
    8
    {
    hero {
    name
    friends {
    name
    }
    }
    }
  • 응답

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    {
    "data": {
    "hero": {
    "name": "R2-D2",
    "friends": [
    {
    "name": "Luke Skywalker"
    },
    {
    "name": "Han Solo"
    },
    {
    "name": "Leia Organa"
    }
    ]
    }
    }
    }

조건 query

인자를 주는 것으로 해당 조건에 맞는 것만을 조회도 가능

  • 요청

    1
    2
    3
    4
    5
    6
    {
    human(id: "1000") {
    name
    height
    }
    }
  • 응답

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "data": {
    "human": {
    "name": "Luke Skywalker",
    "height": 1.72
    }
    }
    }

REST의 경우 단일 인자만 전달 가능한 반면, GraphQL은 객체 전체를 전달도 가능

여러 번의 요청을 한 번에

  • 요청

    • 요청해야 하는 건은 다음과 같다.

      1
      2
      3
      4
      5
      6
      query lifts {
      allLifts {
      name
      status
      }
      }
      1
      2
      3
      4
      5
      6
      query trails {
      allTrails {
      name
      difficulty
      }
      }
    • 그러면 이렇게 하나의 레이블로 묶어 요청도 가능하다

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      query allList {
      allLifts {
      name
      status
      }
      allTrails {
      name
      difficulty
      }
      }
  • 응답

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "data": {
    "allLifts": [
    {
    "name": "Astra Express",
    "status": "OPEN"
    },
    ],
    "allTrails": [
    {
    "name": "Blue Bird",
    "difficulty": "intermediate"
    },
    ]
    }
    }

레이블

일명 작업 타입. 디버깅이나 서버 측에서 로깅하는데 사용하며 문제 발생 시 로그에 찍힌 레이블을 확인해 어디가 문제인지 확인 가능

별칭

DB와 매한가지로, 일명 as 기능을 쿼리 단에서 제공

  • 요청

    1
    2
    3
    4
    5
    6
    7
    8
    {
    empireHero: hero(episode: "EMPIRE") {
    name
    }
    jediHero: hero(episode: "JEDI") {
    name
    }
    }
  • 응답

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "data": {
    "empireHero": {
    "name": "Luke Skywalker"
    },
    "jediHero": {
    "name": "R2-D2"
    }
    }
    }

프래그먼트

재사용 가능한 단위

어떨 때 쓰나요?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
query {
Lift (id: "jazz-cat") {
name
status
capacity
night
elevationGain
trailAccess{
name
difficulty
}
}
Trail(id: "river-run") {
name
difficulty
accessedByLifts {
name
status
capacity
night
elevationGain
}
}
}

현재 name, status, capacity, night, elevationGain 필드가 중복

이런 경우, 서버에서 어떤 타입에 정의된 것인지 on 뒤에 기재해준 뒤 fragment를 먼저 선언하고

1
2
3
4
5
6
7
fragment liftInfo on Lift {
name
status
capacity
night
elevationGain
}

실제 조회 단에서 해당 정의를 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
Lift(id: "jazz-cat") {
...liftInfo
trailAccess{
name
difficulty
}
}
Trail(id: "river-run") {
name
difficulty
accessedByLifts {
...liftInfo
}
}
}

실제로 서버로 요청되는 것은 위에 중복이 많은 코드와 동일

변수

동적 값을 정적으로 타이핑했던 쿼리를 지우고, 이를 별도로 전달하기

  • 요청

    1
    2
    3
    4
    5
    6
    7
    query HeroNameAndFriends($episode: Episode) {
    hero(episode: $episode) {
    name
    friends {
    name
    }
    }
    • 요청 파라미터

      1
      2
      3
      {
      "episode": "JEDI"
      }
  • 응답

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    {
    "data": {
    "hero": {
    "name": "R2-D2",
    "friends": [
    {
    "name": "Luke Skywalker"
    },
    {
    "name": "Han Solo"
    },
    {
    "name": "Leia Organa"
    }
    ]
    }
    }
    }

변수의 정의 ($episode: Episode)

  • $episode의 변수 타입은 Episode임을 선언

  • 모든 변수는 서버에서 일치하는 입력 타입이어야 한다

    아마 위의 예제에서 서버는 Episode 타입이 정의되어 있고, string을 문자열로 요구할 것임을 유추 가능

    자세한 변수 선언은 따로 정리 예정

  • 만약 필수 인자라면

    • ($episode : Episode!)
  • 기본 값을 정의하고자 한다면

    • ($episode: Episode = "JEDI")

mutation

  • 요청

    1
    2
    3
    4
    5
    6
    mutation ($ep: Episode!, $review: ReviewInput!) {
    createReview(episode: $ep, review: $review) {
    stars
    commentary
    }
    }
    1. Episode, ReviewInput 타입을 받는 mutation
    2. 그 중 createReview 타입인 것을 찾아서
    3. episodereview를 넣고
    4. 해당 데이터(막 만든 데이터)의 stars, commentary를 반환
    • 요청 파라미터

      1
      2
      3
      4
      5
      6
      7
      {
      "ep": "JEDI",
      "review": {
      "stars": 5,
      "commentary": "This is a great movie!"
      }
      }
  • 응답

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "data": {
    "createReview": {
    "stars": 5,
    "commentary": "This is a great movie!"
    }
    }
    }

query 필드와 mutation 필드의 차이

  • query 필드는 병렬로 실행
  • mutation 필드는 하나씩 차례로 실행
    • 하나의 요청에서 두 개의 뮤테이션을 보낸다고 가정하면 처음 보낸 요청은 두 번째 요청 전에 완료되는 것이 보장된다

subscription

1
2
3
4
5
6
7
subscription {
liftStatusChange {
name
capacity
status
}
}

위의 쿼리가 실행되면, listen 상태가 되며 다른 요청에 의해 mutation이 되면 name, capacity, status를 반환

facebook에서 누군가 내 게시글에 like를 누르면 알림이 오는 시스템 등에 이용