GraphQL 연재 2편으로, 정의한 스키마에 실제로 질의를 실행하고 결과를 HTTP 로 클라이언트에 전송하는 층을 다룬다. 핵심은 HTTP 전송이 GraphQL 스펙 밖이라 상태 코드 규칙과 엔드포인트 운영을 각 팀이 정해야 한다는 것이다. 카카오스타일이 실제로 잡은 컨벤션과 그게 남긴 한계까지 공개한다.
핵심 포인트- 질의 실행은 GraphQL.js 의 graphql 메소드, GraphQL Java 의 execute 메소드로 한다. 두 라이브러리 모두 질의 처리만 담당하고 서버와 클라이언트 구조는 다루지 않는다.
- HTTP 서빙은 별도 라이브러리 몫이다. Node.js 는 express-graphql 이나 Apollo Server, Java 는 Spring for GraphQL 같은 모듈이 필요하다.
- HTTP 전송은 GraphQL 스펙에 포함되지 않는다. express-graphql 이라는 사실상 표준 구현과 Serving over HTTP 문서가 큰 틀만 정해둔 상태다.
- 실행 결과를 HTTP 상태 코드에 매칭하는 건 의미가 적다. 리소스 둘 중 하나만 없으면 404 인지, Mutation 중 하나만 생성이면 201 인지 답이 없다. 그래서 보통 200, 400, 500 만 쓴다.
- 카카오스타일 규칙은 애플리케이션 오류도 호출 자체는 성공으로 보고 200 을 주되 errors 필드 존재로 판단하는 것이다. 400 과 500 은 사용자가 처리할 수 없는 오류로 한정했다.
상세 정리- express-graphql 내부는 복잡하지 않다. URL 이나 body 에서 query 와 variables 를 꺼내 parse, validate, execute 로 나뉜 메소드로 질의하고 결과를 HTTP 규격에 맞춰 반환한다.
- 상태 코드를 어렵게 만드는 또 하나는 부분 성공이다. data 에 값이 있으면서 errors 도 함께 있는 경우가 존재한다.
- 구현체마다 판단이 다르다. express-graphql 은 data 가 없으면 500 을 반환하지만 Apollo Server 는 순수 에러여도 200 을 반환한다.
- 200 이 아닌 응답은 GraphQL 응답이 아닐 수 있다. 중간 로드밸런서에서 타임아웃이 나 JSON 이 아닌 응답이 왔고, 이를 errors 객체로 파싱하려다 의미 없는 SyntaxError 를 본 경험을 든다.
- 그래서 400 과 500 은 클라이언트를 잘못 작성했거나 서버 접근이 안 되는 등 사용자가 손쓸 수 없는 오류로 정의했다. 많은 HTTP 클라이언트가 이 구간에서 예외를 던지기 때문이다.
- 반면 비밀번호가 틀린 것처럼 사용자가 알아야 하는 애플리케이션 오류는 200 으로 두고, 이후 errors 필드 유무로 판별하도록 규칙을 잡았다.
- 이 규칙의 부작용은 모니터링이다. 호출이 성공으로 잡혀 대부분의 툴이 에러로 세지 않는다. Node.js 는 모니터링 툴에 던지는 데이터를 커스터마이즈해 피했지만 Java 에서는 해결하지 못했고, 규칙을 다시 바꿀 가능성도 열어둔다.
- 엔드포인트가 하나라는 점도 모니터링 문제를 만든다. 기능별 latency 나 에러율을 나눠 볼 수 없다.
- 그래서 클라이언트가 경로 뒤에 operation name 을 붙여 호출하는 컨벤션을 잡았다. 주요 GraphQL 서버 프레임워크는 뒤에 붙은 문자열을 무시하고 동일하게 처리한다.
- 마이크로서비스 간 호출에서는 GetProductList 처럼 operation name 이 겹칠 수 있어, 경로에 서비스명을 먼저 붙이고 두 밑줄로 operation name 을 잇는다.
- API gateway 패턴에서는 한 요청이 여러 서비스 호출로 갈라진다. 사용자 주문 목록 조회 하나가 user 와 order 서비스 호출로 나뉘는 식이라, 상위 요청 이름까지 경로와 operation name 에 담는 컨벤션으로 운영한다.
- AWS 로드밸런서처럼 직접 손댈 수 없는 계층의 로그 확인과 모니터링은 여전히 미해결 과제로 남긴다.
왜 읽나GraphQL 을 운영에 올린 팀이 반드시 부딪히는 상태 코드 규칙과 단일 엔드포인트 모니터링 문제를, 실제 채택한 컨벤션과 그 한계까지 보여주는 글.