GraphQL.js 리졸버의 두 번째 인자인 args를 다룬 연재 글이다. 객체 속성에도 인자를 줄 수 있다는 특성과 null을 준 것과 아예 누락한 것이 구분된다는 점을 실행 결과로 보여준다. 후반부는 상위 필드의 인자를 하위 리졸버에서 써야 하는 목록 페이지네이션 상황을 다룬다.
핵심 포인트- GraphQL에서는 최상위 필드뿐 아니라 객체의 속성에도 인자를 줄 수 있어 서버에서 가공해 내려줄 수 있다.
- nullable 인자에 null을 준 것과 인자를 아예 누락한 것은 서로 다르게 전달된다.
- 리졸버는 어떤 경로로 온 객체인지 몰라도 동작하도록 짜는 것이 바람직하다.
- 그런데 목록과 전체 개수를 함께 반환하는 API에서는 상위 인자가 필요해진다.
- 상위 리졸버가 인자를 담은 객체를 반환하면 하위 리졸버가 source로 받아 쓸 수 있다.
- 대안은 상위에서 둘 다 계산해 반환하는 것인데, 하나만 요청해도 나머지를 계산하는 비효율이 생긴다.
상세 정리- 기본 동작: 해당 필드에 인자가 주어지면 그 값이 두 번째 인자로 들어온다. Java 쪽에서는 환경 객체의 getArguments로 얻는다.
- 예제 구성: 사용자 목록을 반환하는 쿼리에 검색어와 개수 인자를 두고, 사용자 타입의 이름 필드에도 길이 인자를 뒀다.
- 예제 동작: 목록 리졸버는 검색어로 거른 뒤 개수만큼 잘라 반환하고, 이름 리졸버는 길이만큼 문자열을 자른다.
- 세 가지 질의: 검색어와 개수를 모두 준 경우, 개수만 주고 이름에 길이를 준 경우, 두 인자에 명시적으로 null을 준 경우를 한 번에 실행한다.
- 결과의 관전 포인트: 인자를 누락하면 빈 객체가 들어오지만 null을 명시하면 그 키가 null 값으로 들어온다.
- 저자의 견해: 자바스크립트의 null과 undefined 구분은 혼란을 주지만 가끔 유용하다. 다만 GraphQL API는 모든 언어를 고려해야 해 실제로 이 차이를 활용한 적은 없다고 밝힌다.
- Java 구현: DGS Framework로 같은 예제를 짜도 결과가 동일하며 arguments를 출력해보면 null과 누락이 구분되는 것을 확인할 수 있다.
- 상위 인자 문제의 발단: 사용자 타입의 필드 리졸버는 그 객체가 어떤 경로를 거쳐 왔는지 알 수 없으므로 경로에 의존하지 않게 짜야 한다.
- 그럼에도 필요한 경우: 목록 API에서 페이지네이션을 보여주려면 요청한 목록 외에 전체 개수도 필요하고, GraphQL이면 둘을 한 번에 요청할 수 있다.
- 표준 스펙: 페이스북이 GraphQL을 내놓을 때 Relay와 함께 Connections 스펙을 제시했고, 전체 개수와 페이지 정보, edges와 cursor로 구성된다.
- 카카오스타일의 선택: 그 스펙이 복잡하다고 보고 Connection과 Edge, Node 대신 List와 Item 개념을 쓴다. cursor를 위로 빼 Edge를 없애고 다음 페이지 여부는 next_cursor가 null인지로 대체했다.
- 문제 상황: 목록 쿼리에 작성자와 기간, 제목 조건과 offset·limit 인자를 두면, 전체 개수와 목록 리졸버 모두가 그 인자들을 알아야 한다.
- 나쁜 대안: 하위 필드에 같은 인자를 중복해 기술하는 것은 좋아 보이지 않는다.
- 선택한 기법: 상위 리졸버가 인자를 특정 키에 담은 객체를 반환하면 하위 리졸버가 source에서 꺼내 쓴다. 원 리졸버의 args와 헷갈려 익숙해지는 데 시간이 걸리지만 동작한다.
- 다른 대안: 상위에서 전체 개수와 목록을 모두 구해 반환하면 되지만, 둘 중 하나만 요청받아도 나머지를 계산하게 된다. 다음 편에서 다룰 info로 최적화할 수는 있다고 덧붙인다.
- 사용 지침: 상위 인자 접근은 일반적으로 좋지 않은 패턴이므로 목록과 개수처럼 사실상 한 쌍으로 취급되는 경우에만 쓰기를 권한다.
- Java의 대안: GraphQL Java에는 상위 필드가 반환한 값을 별도로 전달받는 localContext 개념이 있어 인자를 그쪽에 실어 보낼 수 있다.
왜 읽나목록과 전체 개수를 함께 내려주는 GraphQL API를 짜다 하위 리졸버가 검색 조건을 몰라 막혔다면 두 가지 우회와 각각의 대가가 정리돼 있다.