02 — 실행 & 리졸버 (Execution & Resolvers)
질문: 쿼리가 들어오면 GraphQL 서버는 그것을 어떻게 평가해서 응답을 만드는가? 한 줄 답: 실행은 스키마 위의 깊이 우선 트리 순회이고, 각 노드의 평가는 리졸버라는 필드 단위 함수다.
이전 챕터(01 — 스키마 & SDL)가 약속을 정의했다면, 이번 챕터는 그 약속이 런타임에 어떻게 지켜지는가다. 한 번의 HTTP POST 요청이 들어오는 순간부터, 응답이 클라이언트가 보낸 쿼리와 완전히 동형인 JSON 트리로 빠져나오기까지의 6단계 여정을 추적한다.
챕터 지도
읽는 순서
| 순서 | 문서 | 누구에게 |
|---|---|---|
| 1 | 01-execution-model | 처음 GraphQL 내부를 보는 사람 — spec의 알고리즘 의사코드부터 |
| 2 | 02-resolver-function | 리졸버를 작성하는 사람 — 4개 인자의 의미 |
| 3 | 03-context-and-info | 인증·DataLoader를 어디에 놓을지 고민하는 사람 |
| 4 | 04-execution-tree-traversal | ”왜 응답 모양이 쿼리와 똑같지?”가 궁금한 사람 |
| 5 | 05-errors-and-partial-response | 에러를 다뤄야 하는 사람 — null propagation 함정 포함 |
| 6 | 06-async-and-promises | Node.js executor의 동작이 궁금한 사람 |
추천 동선: 1·2·4를 먼저 읽으면 멘탈 모델이 완성된다. 그 뒤 5(에러)와 3(context)·6(async)는 실전에서 필요한 순간에 돌아오면 된다.
6개 문서 한 줄 요약
| # | 문서 | 한 줄 답 |
|---|---|---|
| 01 | 실행 모델 | spec §6의 사이클(CollectFields → ExecuteSelectionSet → ExecuteField → CompleteValue)이 실행의 전부다. mutation만 serial, 나머지는 parallel. |
| 02 | 리졸버 함수 | (parent, args, context, info) => value. 4개의 인자는 위치·요청·요청 단위·메타를 분리한다. |
| 03 | Context & Info | context는 요청 1회 동안 살아있는 컨테이너, info는 현재 평가 중인 노드의 자기 인식이다. |
| 04 | 트리 순회 | 응답 JSON은 쿼리 AST와 동형이다 — 이게 GraphQL의 가장 강력한 invariant다. |
| 05 | 에러 & 부분 응답 | data와 errors가 공존한다. non-null 필드가 null이면 부모로 전파된다. |
| 06 | Async & Promise | executor는 Promise를 자동 await 한다. 형제 리졸버는 Promise.all로 동시 진행. |
왜 이 챕터가 중요한가
GraphQL의 신비는 실행 모델을 알면 사라진다.
| 흔한 의문 | 어느 문서가 답하는가 |
|---|---|
| ”왜 응답이 항상 쿼리 모양과 같지?“ | 04 — 응답은 쿼리 트리의 동형 매핑이다 |
| ”내가 쓴 리졸버는 한 번만 등록했는데 왜 100번 호출되지?“ | 01·04 — 리스트 안의 형제마다 한 번씩 호출된다 |
| ”왜 data가 부분만 null이 아니라 전체가 null이지?“ | 05 — non-null이 null이면 부모로 전파된다 |
| ”DataLoader가 어떻게 같은 tick에서 묶이지?“ | 03·06 — context의 생성 시점과 event loop |
| ”mutation을 동시에 보냈는데 왜 순서대로 실행되지?“ | 01 — mutation은 spec이 serial로 강제한다 |
이 답들이 추측이 아니라 spec에서 나온다는 것을 보여주는 게 이 챕터의 목표다.
다음 챕터로
트리 순회가 공짜가 아니라는 사실이 03장의 출발점이다. users { posts { author { name } } } 같은 쿼리는 users 한 번 + posts N번 + author N×M번을 부른다. 이게 그 유명한 N+1 문제다.
다음: 03 — N+1 & DataLoader — 트리 순회가 만드는 fan-out 비용을, 같은 tick에서 batch + cache로 흡수하는 법.