[GraphQL #3] 스키마 설계
Web Frontend Developer

[GraphQL #3] 스키마 설계

해당 포스팅은 프로그래밍 인사이트에서 출판한 <웹 앱 API 개발을 위한 GraphQL> (이브 포셀로, 알렉스 뱅크스 저)을 바탕으로 작성한 글임을 먼저 밝힙니다.

GraphQL API에서 사용하는 데이터 타입의 집합을 스키마(Schema)라고 한다. 백엔드 팀은 스키마를 보고 어떤 데이터를 저장하고 전달해야 하는지 이해할 수 있으며, 프론트엔드 팀은 사용자 인터페이스 작업을 할 때 필요한 데이터를 정의할 수 있다. GraphQL은 스키마 정의를 위해 SDL(Schema Definition Language, 스키마 정의 언어)을 지원한다. 이번 포스팅에서는 GraphQL SDL에 대해서 알아보고자 한다. 이를 위해 간단한 사진 공유 어플리케이션 스키마를 만들어 보려고 한다.

 

타입 정의

사진 공유 어플리케이션의 주요 타입은 User와 Photo이다. 먼저 타입의 경우 필드들로 이루어져 있으며 필드는 각 객체의 데이터와 관련이 있고 특정 종류의 데이터를 반환한다. Photo 타입의 경우 id, name, url, description 등의 필드가 들어갈 수 있을 것이다.

각 필드에는 특정 타입의 데이터가 들어간다. 예를 들면 description, name, url 필드는 String 스칼라 타입을 사용하여 만든다. 필드 쿼리 요청을 보냈을 때 돌아오는 응답 데이터의 형식은 JSON 문자열이다. 참고로 필드에 붙은 문자열은 필드에서 null을 허용하지 않음(non-nullable)을 뜻한다.

Photo의 id에는 각 사진에 대한 고유 식별자가 들어간다. GraphQL에서 ID 스칼라 타입은 고유 식별자 값이 반환되어야 하는 곳에 쓰면 된다.

GraphQL의 내장 스칼라 타입(Int, Float, String, Boolean, ID)은 유용하나, 스칼라 타입을 직접 만들고 싶은 경우도 있다. 아래 예제와 같이 커스텀 스칼라 타입을 만들어서 사용할 수 있다. 커스텀 스칼라 타입의 경우 유효성 검사 방식을 지정할 수가 있다.

 

연결과 리스트

GraphQL 스키마 필드에서는 GraphQL 타입이 담긴 리스트 반환도 가능하다. 리스트는 GraphQL 타입을 대괄호로 감싸서 만든다. 느낌표 때문에 리스트를 정의하는 것이 살짝 헷갈릴 수도 있다. 아래에 여러가지 상황에 대해서 정리를 해 보았다.

  • [Int]: 리스트 안에 담긴 정수 값은 null이 될 수 있다.
  • [Int!]: 리스트 안에 담긴 정수 값은 null이 될 수 없다.
  • [Int]!: 리스트 안의 정수 값은 null이 될 수 있으나, 리스트 자체는 null이 될 수 없다.
  • [Int!]!: 리스트 안의 정수 값은 null이 될 수 없고, 리스트 자체도 null이 될 수 없다.

데이터와 데이터 사이의 관계 정립이 자유로우며, 데이터를 요청할 때 연관된 데이터의 필드까지 요청할 수 있는 쿼리 기능은 GraphQL의 핵심적인 부분이다. 이를 통해 직접 객체 타입 리스트를 만들 때 객체를 서로 연결할 수 있다. 리스트를 가지고 객체 타입을 연결하는 방법에는 세 가지가 있다.

  • 일대일 연결: 하나의 객체 타입이 또 다른 객체 타입과 서로 연결된다. 사진 한 장과 유저 한 명이 연결된다.
  • 일대다 연결: 어떤 객체(부모)의 필드에서 다른 객체 리스트(자식)를 반환하는 필드를 보유하고 있을 때 나타나는 관계이다. 한 명이 여러 장의 사진을 올릴 수 있다.
  • 다대다 연결: 두 개의 일대다 관계로 이루어져 있다. 사진 한 장에 여러 사용자를 태그할 수 있으며 한 명의 사용자는 여러 사진에 태그될 수 있다.

일대일 연결 예제
일대다 연결 예제
다대다 연결 예제

GraphQL 리스트에 항상 같은 타입만 들어가는 것은 아니다. 유니언 타입(Union Type)이나 인터페이스(Interface)를 사용하면 여러 타입을 가지고 쿼리를 만들 수 있다. 먼저 유니언 타입을 살펴보자. 유니언 타입을 사용하면 여러 타입 가운데 하나를 반환할 수 있다. 일정이 스터디 그룹일 때와 운동일 때 각각 반환되는 일정 필드가 달라지는 예제를 한 번 살펴보자.

인터페이스 역시 한 필드 안에 타입을 여러개 넣을 때 사용한다. 객체 타입 용도로 만드는 추상 타입이며, 스키마 코드의 구조를 조직할 때 좋은 방법이다. 인터페이스를 통해 특정 필드가 무조건 특정 타입에 포함되도록 만들 수 있으며, 이들 필드는 쿼리에서 사용할 수 있다. 아래의 agenda 쿼리 예시는 모든 일정 아이템에 반드시 들어가야 하는 필드를 인터페이스에 넣어서 만들어 보았다.

유니언 타입과 인터페이스 둘 다 타입을 여럿 수용하는 필드를 만들 때 사용한다. 둘 중 무엇을 사용할지는 프로젝트의 상황에 따라 달라진다. 일반적으로 객체에 따라 필드가 완전히 달라져야 한다면 유니언 타입을 쓰는 편이 좋다. 반면 특정 필드가 반드시 들어가야 한다면 유니언 타입 대신에 인터페이스가 더 적절하다.

 

인자

GraphQL 필드에는 인자를 추가할 수 있다. 인자를 사용하면 데이터를 전달할 수 있기 때문에 GraphQL 요청 결과 값이 바뀔 수도 있다. 예를 들어 특정한 한 명의 사용자에 대한 이름과 아바타를 선택한다든지, 특정한 사진 한 장의 세부 정보를 받아 보려면 각각 loginID와 photoID를 전달하면 된다.

쌓인 데이터의 양이 많아지면 인자를 통해 반환 데이터의 양을 조절할 수도 있다. 이 때 한 페이지에 나올 양을 정하게 되는데 이를 데이터 페이징(data paging)이라고 한다. 데이터 페이징을 하기 위해서는 first, start 인자를 추가해 주어야 한다. first 인자는 데이터 페이지 한 장 당 들어가는 레코드 수를 의미하며, start는 첫 번째 레코드가 시작되는 인덱스, 즉 시작 위치를 지정하기 위해 사용한다.

데이터 리스트가 반환되는 쿼리를 작성할 때는 리스트의 정렬 방식을 지정할 수도 있다. 이 때는 sort, sortBy 인자를 사용한다. sort는 오름차순으로 할지, 내림차순으로 할지 그리고 sortBy는 정렬 기준으로 사용할 필드를 인자로 넣어줄 수 있다. 예제 코드는 아래와 같이 짜볼 수 있다.

 

뮤테이션

뮤테이션은 반드시 스키마 안에 정의해 두어야 한다. 작성법은 쿼리와 차이가 없지만, 뮤테이션은 어플리케이션의 상태를 바꿀 액션이나 이벤트가 있을 때에만 작성해야 한다. 뮤테이션은 어플리케이션의 동사 역할을 한다. 사용자가 GraphQL 서비스를 가지고 할 수 있는 일을 정의해야 한다.

뮤테이션은 스키마의 루트 mutation 타입에 추가하여 클라이언트에서 사용할 수 있도록 한다. 사진을 게시하는 postPhoto라는 뮤테이션을 만들어 보자. 참고로 뮤테이션을 사용할 때는 데이터를 대량으로 만들어야 할 때가 많으므로 변수를 선언하는 편이 좋다.

뮤테이션에서(또는 쿼리에서) 인자가 많아지고 길어질 수가 있다. 이럴 경우 인풋 타입(input type)을 사용하면 인자 관리를 조금 더 체계적으로 할 수가 있다. 인풋 타입은 GraphQL 객체 타입과 비슷하나, 인풋 타입은 인자에서만 쓰인다. 변수를 받아서 작성한 뮤테이션 코드를 인풋 타입을 통해 조금 바꿔보자.

 

이렇게 GraphQL 스키마의 타입에 대해서 공부해 보았다. 다음 포스팅에서는 본격적으로 GraphQL API를 만드는 내용에 대해서 포스팅을 해 보려고 한다.