GraphQL 스키마와 타입 시스템

sun202x·2022년 10월 14일
0

GraphQL

목록 보기
2/2
post-thumbnail

타입 시스템

모든 graphQL 서비스는 모든 쿼리 가능한 데이터들의 스키마를 정의하고, 쿼리가 들어오면 해당 스키마에 대해 유효성이 검사된 후 실행된다.

객체 타입과 필드

graphQL 스키마의 가장 기본적인 구성 요소는 객체 타입이다. 객체 타입은 서비스에서 가져올 수 있는 객체의 종류와 그 객체의 필드를 나타낸다.

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

위와 같이 type 키워드로 시작하여 객체 타입의 이름을 작성하고 중괄호 안에 필드들의 타입을 작성하는 형식으로 나타낸다. appearsIn 필드를 자세히 보면 배열 타입을 표현한 것을 확인할 수 있다.

인자 타입

graphQL 객체 타입의 모든 필드는 0개 이상의 인수를 가질 수 있다. 모든 인자에는 이름이 있으며, 함수의 인자를 순서대로 가져오는 javascript, python같은 언어들과 다르게 graphQL의 모든 인자는 특별한 이름으로 전달되어진다.

field(first: First, second: Second)와 같이 전달되어져도 graphQL은 인자에 이름이 부여되기 때문에 사용하는 쪽에서 순서와 상관없이 사용되게 된다.

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

위 코드에서 length 필드의 인자는 unit이고 기본값은 METER이다. 인자는 옵셔널일 수 있고 위와 같이 기본 값을 설정할 수 있다.

쿼리타입 & 뮤테이션 타입

스키마 대부분의 타입들은 일반 객체 타입이지만 스키마 내부에는 특수한 타입들이 존재한다.
모든 graphQL 서비스는 query 타입을 가지며 mutation 타입은 가질 수도 있고 가지지 않을 수도 있다. 이 타입 들도 일반 객체 타입과 동일하지만 graphQL에서 정의 해놓은 타입이라는 점이 특별하다.

클라이언트에서 아래와 같이 쿼리를 요청했다면,

query {
  hero {
    name
  }
  droid(id: "2000") {
    name
  }
}

반드시 Query 타입에 존재해야 한다.

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

뮤테이션도 비슷한 방식으로 작동한다.

스칼라 타입

어떤 시점에서 구체적인 데이터로 해석되어야 한다면 스칼라 타입을 이용할 수 있다. 스칼라 타입은 쿼리의 끝을 나타낸다.

여기서는 필드 자체가 쿼리의 실행 요소가 될 수 있기 때문에 쿼리의 끝이라고 설명했다고 본다.

다음 쿼리에서 nameappearIn은 스칼라 타입으로 해석된다.

{
  hero {
    name
    appearsIn
  }
}

# 응답
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ]
    }
  }
}

해당 필드에 하위 필드가 없기 때문에 이를 잘 알 수 있다.

graphQL에서는 기본적인 스칼라 타입이 제공된다.

  • Int: 부호가 있는 32비트 정수
  • Float: 부호가 있는 부동소수점 값
  • String: UTF-8 문자열
  • Boolean: true 또는 false
  • ID: 객체를 다시 요청하거나 캐시의 주요 키로 사용되는 고유 식별자

IDString과 같은 방법으로 직렬화되지만, ID로 정의하는 것은 사람들에게 의도를 전달하기 위해서 이다.

커스텀 스칼라 타입

graphQL에서는 커스텀 스칼라 타입을 지정할 수 있다.

scalar Date

위와 같이 Date타입을 커스텀 스칼라 타입으로 지정하고 해당 타입을 직렬화, 역 직렬화, 유효성 검사하는 방법을 구현할 수 있다. 예를 들어, Date 타입을 항상 정수형 스탬프로 직렬화해야 한다는 것처럼 필요한 것을 지정할 수 있다.

열거형 타입

열거형 타입은 특정 값 들로 제한되는 특별한 종류의 스칼라 타입이다. 아래와 같이 정의할 수 있다.

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

graphQL 서비스는 열거형 타입을 처리할 수 있는 고유한 방법을 가지고 있으며, 이를 통해 열거형 타입이 존재하지 않는 javascript같은 언어에서도 활용될 수 있다. 이러한 세부 정보는 클라이언트에 노출되지 않으며 외부에서 동일하게 사용할 수 있다.

리스트와 Non-Null

graphQL 서비스는 해당 값의 유효성 검사를 할 수 있는 타입 수정자(type modifiers)를 제공해 준다.

Non-Null 타입 수정자

Non-Null 타입 수정자를 통해 필드 뿐만 아니라 인자로 전달되는 경우에도 null 값을 확인하는 작업을 수행할 수 있다.

# 필드에 사용하는 경우
type Character {
  name: String!
  appearsIn: [Episode]!
}

# 인자에 사용하는 경우
query DroidById($id: ID!) {
  droid(id: $id) {
    name
  }
}

위와 같이 정의하면 서버는 ! 키워드를 통해 해당 필드가 null이 아닌 값을 반환할 것으로 기대하며, null 값이 발생되면 graphQL 실행 오류가 발생하고 클라이언트로 알려준다.

List 타입 수정자

List 타입 수정자을 사용하여 해당 타입을 List로 표현할 수 있다.

myField: [String]

위와 같이 대괄호를 묶는 형식으로 사용한다. 유효성 검사 단계에서 해당 값에 대한 배열 요소를 확인하게 된다.

결합

Non-Null과 List 수정자를 결합하여 사용할 수도 있다. 필요에 따라 여러개의 수정자를 중첩하여 사용하면 된다.

# null인 요소를 포함할 수 없다.
myField: [String!]

# 클라이언트
myField: null // valid
myField: [] // valid
myField: ['a', 'b'] // valid
myField: ['a', null, 'b'] // error
# myField가 null이 될 수 없다.
myField: [String]!

# 클라이언트
myField: null // error
myField: [] // valid
myField: ['a', 'b'] // valid
myField: ['a', null, 'b'] // valid
# null인 요소를 포함할 수도 없고 myField가 null일 수도 없다.
myField: [String!]!

# 클라이언트
myField: null // error
myField: [] // valid
myField: ['a', 'b'] // valid
myField: ['a', null, 'b'] // error

인터페이스

여러 타입 시스템처럼 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 인터페이스의 모든 필드를 가지고 각자 별도의 필드를 가졌다.

인터페이스는 객체나 객체 리스트를 반환할 때 유용하지만 특정 타입에만 존재하는 필드를 가져올 때 오류를 반환한다.

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

따라서 특정 객체 타입의 필드를 요청하려면 인라인 프래그먼트를 사용해야 한다.

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
  }
}

유니온 타입

유니온 타입은 인터페이스와 매우 유사하지만, 타입 간에 공통 필드를 특정하지 않는다.

union SearchResult = Human | Droid | Starship

유니온 타입의 멤버는 구체적인 객체 타입 이어야 한다. 인터페이스나 다른 유니온 타입에서 유니온 타입을 사용 할 수 없다. 위와 같은 경우에는 SearchResult 유니언 타입을 반환하는 필드를 받기 위해 어떤 타입이든 받을 수 있는 조건부 프래그먼트를 사용해야 한다.

{
  search(text: "an") {
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}

입력 타입

복잡한 객체를 인자로 전달하기 위해 입력 타입을 정의할 수 있다. 이는 뮤테이션에서 특히 유용한데, 뮤테이션은 생성될 전체 객체를 전달하고자 할 때 사용할 수 있다.

뮤테이션은 보통 수정, 생성, 제거를 하기 때문에 인자가 여러 개가 될 가능성이 높다.

graphQL 스키마 언어에서 입력 타입은 일반 객체 타입과 동일하지만 type 대신 input 키워드를 사용하여 정의한다.

input ReviewInput {
  stars: Int!
  commentary: String
}

입력 객체 타입의 입력 필드는 입력 객체 타입을 참조할 수 있지만, 출력 타입을 스키마에 혼합할 수는 없다. 또한 필드에도 인자를 가질 수 없다.

Reference

GraphQL 공식문서

profile
긍정적으로 살고 싶은 개발자

0개의 댓글