01 · 스키마 & SDL
이 챕터가 답하는 질문: GraphQL 스키마는 무엇이고, SDL로 어떻게 표현하며, 디렉티브의 진짜 역할은 무엇인가? 한 줄 답 (Pyramid Top): “스키마는 클라이언트와 서버 사이의 런타임 강제 가능한 계약이고, SDL은 그 계약을 사람이 읽는 형태로 적은 것이다.”
챕터 지도 (Mermaid)
Why — 왜 이 챕터를 먼저 보나
GraphQL에서 발생하는 사고의 절반은 스키마를 잘못 적은 것에서 출발한다.
- “왜
user.email필드가null로 오지?” → 필드에!를 안 붙였다 → 01-sdl-syntax - “왜
input UserInput을type UserInput으로 적으면 에러나지?” → 인풋과 아웃풋은 분리되어 있다 → 04-input-types-and-arguments - “왜
__typename이 필요하지?” → union·interface의 구체 타입을 구분하려면 → 03-interface-union-enum - “왜
Date스칼라는 스펙에 없지?” → 표준 스칼라는 5종뿐, Date는 커스텀 → 06-custom-scalars - “왜
@deprecated가 코드를 안 바꿀까?” → 디렉티브는 원래 동작 변경자, 표준 ones는 부작용 없음 → 05-directives - “왜 우리 팀은 Nexus를 쓰는데 옆 팀은
.graphql파일을 쓰지?” → schema-first와 code-first의 철학 차이 → 07-schema-first-vs-code-first
이 챕터는 스키마라는 단어 안에 숨어 있는 7개의 개념에 이름을 붙인다.
How — 어떻게 읽나
| # | 문서 | 읽는 데 | 핵심 키워드 |
|---|---|---|---|
| 01 | 01-sdl-syntax | 8분 | type, Query/Mutation/Subscription, !, [T], [T!]! |
| 02 | 02-types-scalars-objects | 10분 | Int/Float/String/Boolean/ID, object type, field, argument |
| 03 | 03-interface-union-enum | 12분 | interface, union, enum, __typename, fragment |
| 04 | 04-input-types-and-arguments | 10분 | input, default value, mutation input pattern |
| 05 | 05-directives | 15분 | @deprecated, @skip, @include, @specifiedBy, custom directive |
| 06 | 06-custom-scalars | 12분 | DateTime, JSON, EmailAddress, parseValue/parseLiteral/serialize |
| 07 | 07-schema-first-vs-code-first | 12분 | schema-first, code-first, Apollo/Yoga vs Pothos/Nexus/TypeGraphQL |
의존성: 02는 01의 문법을 가정한다. 03·04는 02 위에 쌓인다. 05·06은 어디서 읽어도 되지만 04 다음을 추천. 07은 챕터 전체를 어떻게 관리할 것인가로 닫는다.
What — 한 페이지 요약
| 문서 | 한 줄 결론 |
|---|---|
| 01 | SDL의 핵심은 타입과 nullability다. 기본은 nullable이고 !가 붙어야 non-null인 이 결정이 GraphQL의 가장 자주 틀리는 부분이다. |
| 02 | 표준 스칼라는 5종(Int·Float·String·Boolean·ID)뿐이고, 그 외 모든 문자열·정수가 아닌 값은 커스텀 스칼라거나 object type이다. |
| 03 | interface는 공통 필드를 강제하고, union은 공통 필드 없이 선택지를 묶고, enum은 닫힌 집합의 값을 강제한다 — 셋 다 __typename으로 구체 타입을 가른다. |
| 04 | 인풋 타입과 오브젝트 타입은 문법적으로 분리되어 있다 — 인풋은 input-only, 아웃풋은 output-only로 강제한다. |
| 05 | 디렉티브는 주석이 아니라 동작 변경자다. 표준 directive(@deprecated·@skip·@include)는 부작용이 약하지만, 커스텀 directive는 스키마 변환을 한다. |
| 06 | Date·JSON·Email은 모두 커스텀 스칼라다. parseValue / parseLiteral / serialize 세 함수를 적어야 한다. |
| 07 | schema-first(SDL이 SoT)와 code-first(코드가 SoT)는 철학 차이다. 어느 쪽도 우월하지 않고, 팀의 type system 의존도가 답을 정한다. |
What-if — 이 챕터를 건너뛰면
- nullability를 모르면: 클라이언트가
?.체이닝을 도배하다 런타임에서 깨진다. - interface·union을 모르면: union 응답에서
__typename을 안 받아 판별 불가해진다. - input/object 분리를 모르면: 한 타입을 양쪽에서 쓰려다 스키마 등록부터 실패한다.
- directive를 메타데이터로 오해하면:
@auth같은 커스텀 directive가 runtime에서 동작하지 않는 미신적 사고가 생긴다. - schema-first/code-first를 혼동하면: 두 진영 코드가 한 레포에 섞여 SoT가 두 개가 된다.
Insight — 한 단락 이야기
“SDL은 처음엔 spec에 없었다”
2015년 GraphQL 첫 공개 당시, 스키마는 JavaScript 코드로만 정의되었다.
new GraphQLObjectType({ ... }). SDL — 사람이 읽는 문법 — 은 2016년 RFC로 추가되었고 2018년에야 공식 스펙(October 2016 → June 2018)에 포함되었다. 즉 code-first가 원조고 schema-first가 후발이다. 그러나 SDL이 추가된 순간 서버 구현 언어와 무관한 공용어가 생겼다 — Python·Go·Rust 서버도 같은.graphql파일을 읽을 수 있게 된 것이다. 추상화의 출발은 코드였지만, 표준화의 도착지는 텍스트였다.
한 단락 요약
스키마는 런타임 강제력을 가진 계약이고, SDL은 그 계약의 텍스트 표현이다. 이 챕터를 끝내면
Query.user(id: ID!): User한 줄에 들어 있는 7개의 결정(Query라는 root,user라는 필드명,id라는 인자명,ID!라는 non-null 스칼라,User라는 object type, 그 nullability, 그리고 이 한 줄이 SDL인지 코드인지)을 분리해서 읽을 수 있게 된다. 다음 챕터(02-execution-resolvers)는 이 계약을 어떻게 평가하는가를 다룬다.