GraphQL 이란?

graphql

#1

GraphQL는

GraphQL는 API 용 언어입니다. 데이터 베이스(DB)에서 SQL 스키마로 DB에 쿼리를 하고 그 결과를 받듯이, GraphQL로 서버에 요청(Request)하면 그 결과를 돌려줍니다.
데이터 형식만 정의하기 때문에, 특정 언어나 데이터 형식에 영향을 받지 않습니다. 즉 text를 사용해도 되고, DB를 사용해도 되는 것이지요. GraphQL의 정의에 따라 쿼리를 작성하고 서버에 보내면 JSON 형식으로 되돌아 옵니다.

쿼리와 뮤 테이션

GraphQL는 다양한 데이터 검색과 편집에 관한 것들을 가지고 있습니다.

Fields

가장 쉬운 방법은이 Field입니다.

{
  hero {
    name
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

요청한 쿼리와 같은 형태로 Json이 돌아옵니다. GraphQL는 바로 원하는 데이터를 받을 수 있습니다.

String과 int와 같은 것뿐만 아니라 Object도 받을 수 있습니다. 여기서 #은 주석을 나타냅니다.

{
  hero {
    name
    # Queries can have comments!
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

예시는friends 배열의 객체를 돌려줍니다.

Arguments

Field 만으로는 정해진 데이터를 받을 수 밖에 없습니다. 그래서 Arguments를 추가하여 다양한 데이터를 유연하게 받을 수 있습니다.

{
  human(id: "1000") {
    name
    height
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

일반적으로 RESTful API는 URL 쿼리로 밖에는 Arguments를 보낼 수 없습니다. 하지만 GraphQL는 각각의 Field 등에 대해서도 Arguments를 붙일 수 있습니다.

{
  human(id: "1000") {
    name
    height(unit: cm)
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 172
    }
  }
}

전달 인자는 다양한 형태가 있습니다. 스스로 정의해서 사용할 수 있습니다.

Aliases

예를 들어 hero (episode : EMPIRE)hero (episode : JEDI)를 같은 쿼리에서 참조하려고했을 경우, 같은hero 필드가 되어 참조 할 수 없습니다. 그래서 별칭을 사용합니다.

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

별칭을 사용하여 두 결과를받을 수 있습니다.

Fragments

같은 데이터를 받기 위하여 동일한 것을 여러 번 선언하는 것은 중복입니다

{
  leftComparison: hero(episode: EMPIRE) {
    name
    appearsIn
    friends {
      name
    }
  }
  rightComparison: hero(episode: JEDI) {
    name
    appearsIn
    friends {
      name
    }
  }
}

사용하는 것으로 위의 예는 아래와 같이 간단하게 선언할 수 있습니다.

{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}
{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ],
      "friends": [
        { "name": "Han Solo" },
        { "name": "Leia Organa" },
        { "name": "C-3PO" },
        { "name": "R2-D2" }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ],
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

같은 형식의 많은 데이터를 검색 할 때 편리하게 사용할 수 있습니다. 여러개를 나누어서 사용할 수도 있습니다.

Variables

대부분의 앱은 Arguments에 지정하는 것은 다릅니다. GraphQL는 내부에 고유의 형식으로 변환하기 위해 변화하는 Arguments를 직접 쓰는 것은 권유하지 않습니다. 그래서 변수를 사용합시다.

우선 변수를 사용 쿼리를$ 변수 이름과 같이 선언합니다. (1 줄) 그 변수를 사용 쿼리를 지금까지 같이 기술합니다 (2 줄)

query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

변수 이름 : 값과 같이 기술하고 변수 사전을 만듭니다. (Json의 경우가 많다)

{
  "episode": "JEDI"
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

클라이언트 측에서는 새로운 쿼리를 만들 필요는없고, 단지 변수 (Json)를 전달합니다. 변수를 사용하지 않고 쿼리를 문자열 결합으로 만들 수는 없습니다.

변수 정의

변수 정의는($ episode : Episode)과 같이 선언되어 $를 앞에 변수 이름, 그 뒤에 형식이 선언됩니다. 함수 선언과 비슷합니다.

위의 예에서$ episode 필요는 없습니다. 필수하려면($ episode : Episode!)과 같이 선언합니다. (자세한 내용은 앞으로의 스키마와 유형 (# 스키마와 유형)를 참조하십시오.)

query Hero($episode: Episode, $withAppearsIn: Boolean!, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    appearsIn @skip(if: $withAppearsIn)
    friends @include(if: $withFriends) {
      name
    }
  }
}
{
  "episode": "JEDI",
  "withAppearsIn": true,
  "withFriends": true
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

위의 예에서$ with ----변수에 따라서 사용된 것을 알 수 있습니다.

@include (if : Boolean)``true의 경우 표시됩니다 @skip (if : Boolean)``false의 경우 표시됩니다

Mutations

지금까지는 데이터 취득에 대해었지만 이번에는 편집이나 추가 내용입니다 GraphQL에서 데이터를 가져 오는 데 중점을두고 있습니다. 서버 측에서 데이터의 편집도 중요합니다.

REST에GET에서 데이터를 변경하는 것은 권장되지 않도록 GraphQL에도 쿼리에서 데이터를 변경하는 것은 권장되지 않습니다. REST는POST, PUT 등 않았지만 GraphQL는 mutations이 추천되고 있습니다.

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

요청 된 데이터에 대해서 업데이트된 데이터가 반환됩니다. createReviewreview 인수가 단일 값이 아닌 객체 인 것을 알 수 있습니다. 이에 대해서는 앞으로의 스키마와 유형 (# 스키마와 유형)를 참조하십시오

** 또한 query는 병렬로 실행되지만 mutations은 순차적으로 실행됩니다. ** 이것은 1 요청에서 여러 mutations을 실행해도 반드시 첫 번째 종료 후 두 번째가 실행되는 것을 보장 된 데이터가 충돌하지 않는 것이 보증됩니다.

Inline Fragments

GraphQL는 형식을 인터페이스를 사용하여 표현하는 것도 가능합니다. (자세한 내용은 앞으로의 스키마와 유형 (# 스키마와 유형)를 참조하십시오)

형태에 따라 취득하는 데이터를 변경할 수 있습니다.

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}
{
  "ep": "JEDI"
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

hero는 Droid 하나 Human가 반환됩니다 (인터페이스 인 Charactor가 반환됩니다), 그에 따라 바뀌었습니다.

특수 필드

요청에__typename을 포함하여 반환된 형태를 참조할 수 있습니다.

{
  search(text: "an") {
    __typename
    ... on Human {
      name
    }
    ... on Droid {
      name
    }
    ... on Starship {
      name
    }
  }
}
{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo"
      },
      {
        "__typename": "Human",
        "name": "Leia Organa"
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1"
      }
    ]
  }
}

또한 이러한 특별한 것은 앞으로의 Introspection (# Introspection)을 참조하십시오.

스키마와 유형

GraphQL 구현 된 언어에 의존하지 않도록 GraphQL 고유 한 형식 개념이 존재합니다.

Object types and fields

GraphQL 스키마의 가장 기본적인 구성 요소는 개체 형식입니다. 이것은 서비스에서 얻을 수있는 개체의 유형과 필드를 나타냅니다. 스키마는 다음과 같이 쓰고 있습니다.

type Character {
  name: String!
  appearsIn: [Episode]!
}
  • Character는 GraphQL의 개체 형식입니다. 여러 필드를가집니다. GraphQL 스키마는 대부분이 객체 형입니다.
  • nameappearsInCharacter 형의 필드입니다. 이들은 Query에서 참조 할 수 있습니다.
  • String 구현 된 스칼라입니다. 자세한 것은 나중에.
  • String!필드가 null가 아닌 것을 보장합니다. 즉,이 필드를 참조하면 항상 값이 돌아옵니다.
  • [Episode!]Episode 객체의 배열을 나타냅니다. 또한 null로없는 때문에 항상 배열을 반환합니다. (배열의 크기가 0 포함)

Arguments

GraphQL 필드에는 인수를 붙일 수 있습니다.

type Starship {
  id: ID!
  name: String!
  length(unit: LengthUnit = METER): Float
}

위의 예에서는length 필드에 하나의 필드를 정의합니다. 인수는 필수가 아니라 기본을 설정 할 수 위의 예에서는 디폴트로METER이 지정되어 있습니다.

The Query and Mutation types

스키마는 보통 개체 형식이지만 특별한 두 스키마가 있습니다.

schema {
  query: Query
  mutation: Mutation
}

모든 GraphQL 서비스에는query 형이mutation 형일 수도 있습니다. 모든 쿼리는 처음 시작할 때 정의됩니다.

예를 들어

query {
  hero {
    name
  }
  droid(id: "2000") {
    name
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2"
    },
    "droid": {
      "name": "C-3PO"
    }
  }
}

같은 쿼리가 실행 된 경우Query 형이 다음과 같은hero``droid 필드를 정의해야합니다.

type Query {
  hero(episode: Episode): Character
  droid(id: ID!): Droid
}

Mutations 역시Mutation 형 필드에 정의되어야 합니다. 스키마에 대한 시작점이라는 점이외는 객체 형과 같은 필드도 같은 방식으로 작동합니다.

Scalar types

객체 형에는 이름과 필드가 정의되지만, 어딘가에서 그 필드를 구체적인 데이터에 해결해야합니다. 이것이 스칼라입니다. GraphQL에는 기본적으로 다음의 스칼라 형의 세트가 준비되어 있습니다.

스칼라 이름 내용
Int 부호있는 32 비트 정수
Float 부호 배정 밀도 부동 소수점 값
String UTF-8 문자 순서
Boolean true 또는 false
ID 고유 값이 정의되어 재 취득시 등에 데이터를 검증하기

물론 스스로 스칼라 형을 정의 할 수 있습니다.

scalar Date

이 형태를 직렬화, 역 직렬화, 유효성 검사하는 것은 내부 구현에 따라 달라집니다.

Enumeration types

열거 형은 정해진 값의 요소를 정의하지만 그 안에 중복 값을 선언하는 것은 허용되지 않습니다.

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

위의 예는Episode 열거 형을 정의하고NEWHOPE``EMPIRE``JEDI 값을 선언합니다.

다양한 언어의 GraphQL 구현 언어에 존재하는 Enum을 이용하거나, Javascript처럼 Enum이 존재하지 않는 것은 구현할 할지도 모르지만, 그것은 클라이언트에 관계없이 GraphQL 로 enum로서 완벽하게 작동합니다.

Lists and Non-Null

객체 형 스칼라 형 및 열거 GraphQL 정의 할 수있는 형태입니다. 그러나 실제로 사용하는 경우에는이 외에 추가 유형 수정자를 적용 할 수 있습니다.

type Character {
  name: String!
  appearsIn: [Episode]!
}

위의 예에서는String 형에!를 켜고 null 을 사용합니다.

query searchWord($word: String!)
{
  "word": null
}

例えば上記の様に宣言し, 実行すると以下のような検証エラーを出力します.

{
  "errors": [
    {
      "message": "Variable \"$word\" of required type \"String!\" was not provided.",
      "locations": [
        {
          "line": 1,
          "column": 18
        }
      ]
    }
  ]
}

목록도 같은 방식으로 작동합니다. 예를 들어[String]같이하면 String 형의 배열을 돌려줍니다. ![]는 혼합시킬 수 있습니다.

fieldName: [String!]

하면 목록 자체는 null이라도 좋지만, null 요소는 존재하지 않습니다. 다음과 같이 대응됩니다

field의 내용 오류 여부
null
[]
[ “a”, “b”]
[ “a”, null, “b”]

null이 아닌 비 허용 형 목록을 정의하면

fieldName: [String]!
field의 내용 오류 여부
null
[]
[ “a”, “b”]
[ “a”, null, “b”]

과 같이됩니다.

Interfaces

다른 언어와 마찬가지로 GraphQL도 인터페이스를 지원하고 있습니다. 인터페이스는 그 인터페이스를 구현하기 위해 포함해야 필드를 정의하는 추상입니다.

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

즉 Character를 구현하는 모든 형식은 이들을 구현해야합니다.

type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  primaryFunction: String
}

Character 인터페이스에 정의 된 필드뿐만 아니라totalCredits``starships``primaryFunction처럼 그 형태 자체 필드를 선언 할 수 있습니다.

인터페이스는 편리하지만 사용에주의하십시오.

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    primaryFunction
  }
}
{
  "ep": "JEDI"
}
{
  "errors": [
    {
      "message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Droid\"?",
      "locations": [
        {
          "line": 4,
          "column": 5
        }
      ]
    }
  ]
}

primaryFunction 필드는Droid 형 밖에 선언되어 있지 않기 때문에 오류가 발생합니다.

conditional fragment

이를 방지하기 위해 인라인 조각을 사용하여Droid 형의 경우 만 가져 오도록 할 수 있습니다.

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

인라인 조각에 관해서는 [여기] (# inline-fragments)을 참조하십시오.

union types

공용체는 인터페이스와 매우 비슷하지만 각각 동일한 필드를 정의 할 필요가 없습니다.

union SearchResult = Human | Droid | Starship

SearchResultHuman``Droid``Starship 중 하나를 반환합니다.

공용체는 구체적인 개체 형식을 지정해야 인터페이스 및 기타 공용체는 지정할 수 없습니다.

SearchResult 공용체를 반환 필드 참조하는 경우 조건부 조각 (# conditional-fragment)을 사용하여 참조합니다.

{
  search(text: "an") {
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}
{
  "data": {
    "search": [
      {
        "name": "Han Solo",
        "height": 1.8
      },
      {
        "name": "Leia Organa",
        "height": 1.5
      },
      {
        "name": "TIE Advanced x1",
        "length": 9.2
      }
    ]
  }
}

Input types

지금까지는 enum과 string 고 말했다 스칼라 형을 인수로 필드에 전달하는 방법을 설명했습니다. GraphQL에서뿐만 아니라 개체 형식을 쉽게 전달할 수 있습니다. (특히 mutations에서 자주 사용) 객체 형처럼 보이지만type 대신input 키워드를 사용합니다.

input ReviewInput {
  stars: Int!
  commentary: String
}

과 같이 사용합니다

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

input 형식의 필드에 input 형식을 지정할 수 있지만 출력은 input 형식과 같은 형태로 출력되는 것은 아닙니다. 또한 input 형식은 필드에 옵션을 쓰면 수 없습니다.

참고 문헌

[Introduction to GraphQL (http://graphql.org/learn/)