모든 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
}
뮤테이션도 비슷한 방식으로 작동한다.
어떤 시점에서 구체적인 데이터로 해석되어야 한다면 스칼라 타입을 이용할 수 있다. 스칼라 타입은 쿼리의 끝을 나타낸다.
여기서는 필드 자체가 쿼리의 실행 요소가 될 수 있기 때문에 쿼리의 끝이라고 설명했다고 본다.
다음 쿼리에서 name
과 appearIn
은 스칼라 타입으로 해석된다.
{
hero {
name
appearsIn
}
}
# 응답
{
"data": {
"hero": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
}
}
}
해당 필드에 하위 필드가 없기 때문에 이를 잘 알 수 있다.
graphQL
에서는 기본적인 스칼라 타입이 제공된다.
Int
: 부호가 있는 32비트 정수Float
: 부호가 있는 부동소수점 값String
: UTF-8 문자열Boolean
: true 또는 falseID
: 객체를 다시 요청하거나 캐시의 주요 키로 사용되는 고유 식별자
ID
는String
과 같은 방법으로 직렬화되지만,ID
로 정의하는 것은 사람들에게 의도를 전달하기 위해서 이다.
graphQL
에서는 커스텀 스칼라 타입을 지정할 수 있다.
scalar Date
위와 같이 Date
타입을 커스텀 스칼라 타입으로 지정하고 해당 타입을 직렬화, 역 직렬화, 유효성 검사하는 방법을 구현할 수 있다. 예를 들어, Date
타입을 항상 정수형 스탬프로 직렬화해야 한다는 것처럼 필요한 것을 지정할 수 있다.
열거형 타입은 특정 값 들로 제한되는 특별한 종류의 스칼라 타입이다. 아래와 같이 정의할 수 있다.
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
graphQL
서비스는 열거형 타입을 처리할 수 있는 고유한 방법을 가지고 있으며, 이를 통해 열거형 타입이 존재하지 않는 javascript
같은 언어에서도 활용될 수 있다. 이러한 세부 정보는 클라이언트에 노출되지 않으며 외부에서 동일하게 사용할 수 있다.
graphQL
서비스는 해당 값의 유효성 검사를 할 수 있는 타입 수정자(type modifiers)를 제공해 준다.
Non-Null 타입 수정자
를 통해 필드 뿐만 아니라 인자로 전달되는 경우에도 null
값을 확인하는 작업을 수행할 수 있다.
# 필드에 사용하는 경우
type Character {
name: String!
appearsIn: [Episode]!
}
# 인자에 사용하는 경우
query DroidById($id: ID!) {
droid(id: $id) {
name
}
}
위와 같이 정의하면 서버는 !
키워드를 통해 해당 필드가 null
이 아닌 값을 반환할 것으로 기대하며, null
값이 발생되면 graphQL
실행 오류가 발생하고 클라이언트로 알려준다.
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
}
입력 객체 타입의 입력 필드는 입력 객체 타입을 참조할 수 있지만, 출력 타입을 스키마에 혼합할 수는 없다. 또한 필드에도 인자를 가질 수 없다.