GraphQL 유형 시스템 이해하기

작가는 Free and Open Source Fund를 기부금 수령 대상으로 Write for DOnations 프로그램의 일부로 선택했습니다.

소개

GraphQL은 프론트 엔드와 데이터 소스 간의 통신을 원활하게 도와주는 현대적인 솔루션입니다. GraphQL의 모든 세부 정보와 기능은 GraphQL 스키마에 기술되어 있습니다. 기능하는 GraphQL 스키마를 작성하기 위해서는 GraphQL 타입 시스템을 이해해야 합니다.

이 글에서는 GraphQL 타입에 대해 알아보겠습니다. 내장된 다섯 가지 스칼라 타입, 열거형, 리스트와 비-널 래핑 타입, 객체 타입, 그리고 이와 함께 작동하는 추상적인 인터페이스유니온 타입에 대해 알아볼 것입니다. 각 타입에 대한 예제를 살펴보고 완전한 GraphQL 스키마를 구축하는 방법을 배우게 될 것입니다.

전제 조건

이 튜토리얼을 최대한 활용하기 위해서는 다음을 준비해야 합니다:

스칼라 타입

GraphQL 스키마의 모든 데이터는 기본값을 나타내는 다양한 스칼라 타입으로 해결됩니다. GraphQL 응답은 트리 형태로 나타낼 수 있으며, 스칼라 타입은 트리의 끝에 있는 리프(leaf)입니다. 중첩된 응답에는 여러 수준이 있을 수 있지만, 마지막 수준은 항상 스칼라(또는 Enum) 타입으로 해결됩니다. GraphQL에는 다섯 가지 내장 스칼라 타입이 있습니다: Int, Float, String, Boolean, ID.

Int

Int는 부호 있는 32비트 소수점이 없는 숫자 값입니다. 부호 (양수 또는 음수)가 포함되지만 소수점은 없는 정수입니다. 부호 있는 32비트 정수의 최대값은 2,147,483,647입니다. 이는 숫자 데이터에 사용되는 두 가지 내장 스칼라 중 하나입니다.

Float

A Float is a signed double-precision fractional value. It is a signed (positive or negative) number that contains a decimal point, such as 1.2. This is the other built-in scalar used for numerical data.

String

A String is a UTF-8 character sequence. The String type is used for any textual data. This can also include data like very large numbers. Most custom scalars will be types of string data.

Boolean

A Boolean is a true or false value.

ID

ID는 고유 식별자입니다. 이 값은 항상 문자열로 직렬화되며, ID가 숫자일 경우에도 그렇습니다. ID 유형은 일반적으로 Universally Unique Identifier (UUID)로 표현될 수 있습니다.

사용자 지정 스칼라

내장 스칼라 외에도, scalar 키워드를 사용하여 사용자 지정 스칼라를 정의할 수 있습니다. 사용자 지정 스칼라를 사용하여 Date, Time, 또는 Url과 같은 추가 서버 수준 유효성 검사를 가진 유형을 생성할 수 있습니다. 다음은 새로운 Date 유형을 정의하는 예시입니다:

scalar Date

서버는 이 새로운 유형과 상호 작용하는 방법을 GraphQLScalarType을 사용하여 알고 있을 것입니다.

열거형 유형

열거형 유형은 가능한 값 집합을 설명하는 유형입니다.

GraphQL로 데이터 관리하는 방법 시리즈의 다른 튜토리얼에서 사용하는 Fantasy 게임 API 테마를 사용하여, 게임 캐릭터의 JobSpecies를 위한 enum을 만들 수 있습니다. Enum은 다음과 같이 enum 키워드로 정의됩니다:

"The job class of the character."
enum Job {
  FIGHTER
  WIZARD
}

"The species or ancestry of the character."
enum Species {
  HUMAN
  ELF
  DWARF
}

이렇게 하면 캐릭터의 JobFIGHTER 또는 WIZARD로 보장되며 실수로 "purple" 또는 다른 임의의 문자열로 설정되지 않습니다. 이는 String 유형을 사용하는 대신 사용자 정의 Enum을 만드는 경우에만 가능합니다. Enum은 관례상 모두 대문자로 작성됩니다.

또한 Enum은 인수로 허용되는 값으로 사용할 수 있습니다. 예를 들어, 무기가 한 손으로 사용되는지 두 손으로 사용되는지를 나타내는 Hand Enum을 만들고, 이를 사용하여 장착 가능한 무기가 하나인지 두 개인지를 결정할 수 있습니다:

enum Hand {
  SINGLE
  DOUBLE
}

"A valiant weapon wielded by a fighter."
type Weapon {
  name: String!
  attack: Int
  range: Int
  hand: Hand
}

type Query {
  weapons(hand: Hand = SINGLE): [Weapon]
}

Hand Enum은 SINGLEDOUBLE을 값으로 가지며, weapons 필드의 인수에는 기본값으로 SINGLE이 지정되어 있습니다. 따라서 인수가 전달되지 않으면 SINGLE로 기본 설정됩니다.

Non-Null Type

많은 언어에서 기본적으로 고려하는 null 또는 undefined와 같은 일반적인 유형이 내장 스칼라 목록에서 누락되었음을 알 수 있습니다. Null은 GraphQL에 존재하며 값이 없음을 나타냅니다.

GraphQL 모든 유형은 기본적으로 null이 가능하며 따라서 null은 모든 유형에 대한 유효한 응답입니다. 값이 필요한 경우, GraphQL Non-Null 유형으로 변환해야 합니다. Non-Null은 유형 수정자로 정의되며 수정하려는 유형을 수정하는 데 사용되는 유형입니다. 예를 들어, String은 선택적(또는 null 가능한) 문자열이고, String!은 필수(또는 Non-Null) 문자열입니다.

리스트 유형

A List type in GraphQL is another type modifier. Any type that is wrapped in square brackets ([]) becomes a List type, which is a collection that defines the type of each item in a list.

예를 들어, [Int]로 정의된 유형은 Int 유형의 컬렉션이 되고, [String]String 유형의 컬렉션이 됩니다. Non-Null과 List는 함께 사용하여 유형을 필수로 지정하고 리스트로 정의할 수도 있습니다. 예를 들어, [String]!는 필수이면서 리스트로 정의된 유형입니다.

객체 유형

GraphQL 스칼라 유형이 계층적인 GraphQL 응답의 “말단”을 설명한다면, 객체 유형은 중간 “가지”를 설명하며, GraphQL 스키마의 거의 모든 것이 객체 유형입니다.

객체는 명명된 필드(키)의 목록과 각 필드가 해결되는 값 유형으로 구성됩니다. 객체는 type 키워드로 정의됩니다. 하나 이상의 필드를 정의해야 하며, 필드는 GraphQL 인트로스펙션 시스템과의 충돌을 피하기 위해 언더스코어 두 개로 시작할 수 없습니다.

GraphQL 판타지 게임 API 예제에서 게임 내 캐릭터 유형을 나타내는 Fighter 객체를 생성할 수 있습니다:

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
}

이 예제에서 Fighter 객체 유형이 선언되었으며, 네 개의 명명된 필드를 가지고 있습니다:

  • id는 Non-Null ID 유형을 반환합니다.
  • name은 Non-Null String 유형을 반환합니다.
  • levelInt 유형을 반환합니다.
  • active은 Non-Null Boolean 유형을 반환합니다.

선언 위에는 이 예제의 설명으로 쌍따옴표를 사용하여 주석을 추가할 수도 있습니다: "직접적인 전투 능력과 힘을 가진 영웅.". 이는 유형에 대한 설명으로 표시됩니다.

이 예제에서 각 필드는 스칼라 유형에 해결되지만, 객체 필드는 다른 객체 유형에 해결될 수도 있습니다. 예를 들어, Weapon 유형을 생성할 수 있고 GraphQL 스키마를 설정하여 Fighterweapon 필드가 Weapon 객체로 해결되도록 할 수 있습니다:

"A valiant weapon wielded by a fighter."
type Weapon {
  name: String!
  attack: Int
  range: Int
}

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
  weapon: Weapon
}

객체는 다른 객체의 필드에 중첩될 수도 있습니다.

루트 작업 유형

GraphQL 스키마에는 세 가지 특수한 개체인 Query, Mutation 및 Subscription이 있는데, 이들은 루트 작업 유형으로 알려져 있으며 다른 모든 개체 유형과 동일한 규칙을 따릅니다.

schema 키워드는 GraphQL 스키마의 진입점을 나타냅니다. 루트 Query, Mutation 및 Subcription 유형은 루트 schema 개체에 위치합니다:

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

Query 유형은 GraphQL 스키마에서 필수이며 REST API의 GET과 유사한 읽기 요청을 나타냅니다. 다음은 Fighter 유형의 목록을 반환하는 루트 Query 개체의 예입니다:

type Query {
  fighters: [Fighter]
}

Mutations는 쓰기 요청을 나타내며 REST API의 POST, PUT, 또는 DELETE와 유사합니다. 다음 예에서 Mutation은 이름 있는 인수 (input)를 가진 addFighter 필드를 가지고 있습니다:

type Mutation {
  addFighter(input: FighterInput): Fighter
}

마지막으로, Subscription은 이벤트 스트림에 해당하며 웹 앱에서 Websocket과 함께 사용될 수 있습니다. GraphQL Fantasy API에서는 랜덤 전투를 위해 사용될 수 있습니다. 예를 들면 다음과 같습니다:

type Subscription {
  randomBattle(enemy: Enemy): BattleResult
}

schema 진입점은 일부 GraphQL 구현에서 종종 추상화됩니다.

필드 인수

GraphQL 객체의 필드는 기본적으로 값이 반환되는 함수이며, 다른 함수와 마찬가지로 인수를 받을 수 있습니다. 필드 인수는 인수의 이름 다음에 타입으로 정의됩니다. 인수는 객체 타입이 아닌 모든 타입이 될 수 있습니다. 이 예제에서 Fighter 객체는 id 필드로 (NonNull ID 타입으로 해결됨) 필터링될 수 있습니다:

type Query {
  fighter(id: ID!): Fighter
}

이 구체적인 예제는 데이터 저장소에서 단일 아이템을 가져오는 데 유용하지만, 인수는 필터링, 페이징 및 기타 더 구체적인 쿼리에도 사용할 수 있습니다.

인터페이스 타입

객체 타입과 마찬가지로 추상적인 인터페이스 타입은 이름이 지정된 필드 목록과 그에 따른 값 타입으로 구성됩니다. 인터페이스는 객체의 일부 구현을 정의하기 위해 객체와 동일한 규칙을 따릅니다.

지금까지 스키마에서는 Fighter 객체만 가지고 있지만, Wizard, Healer 등 다른 객체들을 만들고 싶을 수도 있습니다. 이들은 많은 필드를 공유하지만 몇 가지 차이점이 있을 수 있습니다. 이 경우, 공통적으로 갖는 필드를 정의하기 위해 인터페이스를 사용하고, 인터페이스의 구현체인 객체들을 생성할 수 있습니다.

다음 예시에서는, 모든 종류의 캐릭터가 갖는 필드들을 interface 키워드를 사용하여 BaseCharacter 인터페이스로 만들 수 있습니다:

"A hero on a quest."
interface BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job
}

각 캐릭터 유형은 id, name, level, species, job 필드를 갖습니다.

이제, 공유 필드를 갖는 Fighter 타입과 Wizard 타입이 있다고 상상해보세요. 그러나 FighterWeapon을 사용하고, WizardSpells을 사용합니다. 각각을 BaseCharacter 구현체로 나타내기 위해 implements 키워드를 사용할 수 있습니다. 이는 생성된 인터페이스의 모든 필드를 가져야 함을 의미합니다:

"A hero with direct combat ability and strength."
type Fighter implements BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job!
  weapon: Weapon
}

"A hero with a variety of magical powers."
type Wizard implements BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job!
  spells: [Spell]
}

FighterWizard는 모두 BaseCharacter 인터페이스의 유효한 구현체입니다. 왜냐하면 필요한 필드의 하위 집합을 갖고 있기 때문입니다.

유니언 타입

객체와 함께 사용할 수 있는 또 다른 추상 유형은 Union 유형입니다. union 키워드를 사용하여 응답으로 모두 유효한 객체 목록을 정의할 수 있습니다.

이전 섹션에서 생성한 인터페이스를 사용하여 Character Union을 생성할 수 있습니다. 이는 캐릭터를 Wizard 또는 Fighter로 정의합니다.

union Character = Wizard | Fighter

등호 기호(=)는 정의를 설정하고, 파이프 기호(|)는 OR 문으로 작동합니다. Union은 객체 또는 인터페이스로 구성되어야 합니다. 스칼라 유형은 Union에 유효하지 않습니다.

이제 캐릭터 목록을 조회하면 Character Union을 사용하여 모든 WizardFighter 유형을 반환할 수 있습니다.

결론

이 튜토리얼에서는 GraphQL 유형 시스템을 정의하는 여러 가지 유형에 대해 배웠습니다. 가장 기본적인 유형은 스칼라 유형으로, 스키마 트리의 말단에 위치하는 값으로, Int, Float, String, Boolean, ID 및 GraphQL 구현에서 만들어진 사용자 정의 스칼라로 구성됩니다. 열거형은 응답을 단순히 String으로 선언하는 것보다 더 많은 제어가 필요한 경우에 사용할 수 있는 유효한 상수 값의 목록이며, 스키마 트리의 말단입니다. 목록 및 비-널 유형은 유형 수정자 또는 래핑 유형으로 알려져 있으며, 각각 컬렉션 또는 필수로 다른 유형을 정의할 수 있습니다. 객체는 스키마 트리의 가지이며, GraphQL 스키마의 거의 모든 것은 query, mutation, subscription 진입점을 포함하여 객체의 유형입니다. 인터페이스 및 유니언 유형은 객체를 정의하는 데 도움이 되는 추상 유형입니다.

더 자세한 학습을 위해 작동하는 GraphQL 서버 환경을 갖기 위해 Node.js에서 GraphQL API 서버를 설정하는 방법 튜토리얼을 읽고 GraphQL 스키마를 생성하고 수정하는 연습을 할 수 있습니다.

Source:
https://www.digitalocean.com/community/conceptual-articles/understanding-the-graphql-type-system