GraphQL.js 리졸버의 세 번째 인자인 context를 다룬 연재 글이다. 매 요청마다 새로 생성돼 같은 요청을 처리하는 리졸버끼리 상태를 공유하는 용도이며, 실제로 값을 넣고 실행해 동작을 확인한다. DataLoader를 요청별로 만들 때 context를 쓰는 패턴이 후반부의 핵심이다.
핵심 포인트- context는 온전히 사용자가 설정하며 매 요청마다 새로 생성된다.
- 같은 요청을 처리하는 리졸버들이 값을 공유하고 누적할 수 있다.
- 가장 흔한 용도는 HTTP 헤더나 로그인 사용자 정보 같은 요청 부가 정보를 담는 것이다.
- Apollo Server에서는 context 함수에서 req와 res를 그대로 넘겨 리졸버가 헤더를 읽게 한다.
- 리졸버를 독립적으로 두면 N+1 쿼리 문제가 생기는데 DataLoader가 이를 해결한다.
- 사용자마다 접근 권한이 다르므로 캐시를 공유하면 위험해 DataLoader는 요청별로 만들어야 한다.
상세 정리- 동작 확인 방법: 리졸버에서 context를 출력하고 값을 하나씩 늘리는 코드를 두 번의 실행에 서로 다른 초기값으로 돌려본다.
- 실행 결과의 의미: 첫 실행에서는 초기값만 있다가 하위 리졸버를 지날 때마다 값이 증가하고, 두 번째 실행에서는 그 실행에서 준 초기값 기준으로 다시 누적된다. 요청 간에는 섞이지 않는다는 뜻이다.
- 공유 범위: 한 요청 안에서 Query 리졸버와 그 아래 필드 리졸버가 같은 객체를 본다.
- 실무 용도: 요청 시 준 HTTP 헤더나 로그인 상태인 경우의 사용자 정보를 담는 것이 일반적이다.
- Apollo Server 예제: startStandaloneServer의 context 옵션에서 req와 res를 담아 반환하면 리졸버에서 헤더를 그대로 읽을 수 있다.
- Java 대응: GraphQL Java는 GraphQLContext 속성으로 제공하고, DGS Framework는 HTTP 요청을 처리하며 그것을 감싼 DgsContext를 준다.
- DGS 확인 방법: requestData를 출력하면 extensions와 headers, webRequest가 담긴 객체를 볼 수 있다.
- N+1의 발생: 리졸버가 독립적으로 동작하는 편이 좋다 보니 한 번에 요청할 수 있는 것을 나눠서 요청하게 된다.
- DataLoader의 전제: 여러 사용자의 요청을 처리하는 서버에서 캐시를 공유하면 권한이 다른 사용자 간에 데이터가 섞일 수 있어 요청마다 새로 만들기를 권장한다.
- 카카오스타일의 패턴: context에 loader라는 이름의 맵을 두고, 리졸버에서 이름으로 조회해 없으면 그때 DataLoader를 만들어 맵에 넣고 쓴다.
- 패턴의 이점: 필요한 시점에만 인스턴스를 만들면서도 같은 요청 안에서는 재사용된다.
- 초기화 위치: Apollo Server의 context 함수에서 빈 loader 맵을 만들어 넘긴다.
- Java 대응: GraphQL Java는 DataLoader 처리가 라이브러리 안에 들어와 있어 DataLoaderRegistry를 만들어 ExecutionInput으로 주고 DataFetchingEnvironment에서 꺼낸다.
- DGS 대응: DgsDataLoader 어노테이션으로 등록하면 된다.
왜 읽나GraphQL 서버에서 로그인 정보를 리졸버마다 다시 파싱하고 있거나 DataLoader를 전역에 하나 두고 있다면, context를 쓰는 표준 위치와 요청별 인스턴스 패턴을 확인할 수 있다.