Learn GraphQL and Tips
- 저는 리액트를 주로 사용하는 프론트엔드 개발자입니다. 개발을 해오며 필요한 데이터들은 잘 알 수 없는 어딘가에서 가져와서 쓰곤 했습니다. 어느순간 그 어딘가에 대한 궁금증이 생겼고, 그것을 해결하고자 하는 갈증이 생겼습니다. 어디서부터 시작할까 생각을 하다 공부를 해보자, 그리고 써보자 라는 생각이 들어 공부할 대상을 찾다가 GraphQL을 접하게 되었습니다. 강의를 들으며 프로젝트를 만들어보기도 하고, 혼자 개인 프로젝트를 구성해보기도 해보았습니다. GraphQL 이 아닌 형태로 혼자 서버를 구성해본 경험이 없기에 얼마나 편리한지, 얼마나 좋은지에 대한 객관적인 판단은 어렵지만 굉장히 어렵지 않게(물론 아직 모르는게 더 많지만) 사용방법을 파악하고, 재미있게 써볼 수 있었습니다. 그리고 실무에서 GraphQL 을 적용하며 얻은 장점들을 이야기하는 글들도 꽤 접해오고 있어서 조금 더 알아보고 정리해 두어야겠다고 생각했습니다.
이 글을 GraphQL 문서를 기반으로 나름의 활용방법과, 그 밖의 best practice 를 정리해두려 합니다. 기본적으로 publish 하는 목적이 아닌 개인 정리의 목적이므로 스스로 부담을 느끼지 않을 만큼 편히 작성하려 하기에 조금 더 정확하고 자세한 정보는 공식 문서를 참고하시기 바랍니다.
(영어: https://graphql.org/learn/,
한글: https://graphql-kr.github.io/learn/)
처음부터 GraphQL 의 전부를 다루기는 어려울 것이라 생각하고, 공부를 해 나가면서 내용을 추가하는 방식으로 포스팅을 진행하겠습니다.
[Introduction to GraphQL]
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.
GraphQL 사이트의 Learn 부분을 시작하며 볼 수 있는 문구입니다. 다른 언어들과 같이 역시 이 문장에서 중요한 요소들을 많이 발견할 수 있습니다.
GraphQL 은 query lnaguage 이며, type system 을 사용합니다.
query language 의 뜻을 찾아보면 간단히 “질의어(영어: query language)는 데이터베이스와 정보 시스템에 질의를 할 수 있게 하는 고급 컴퓨터 언어이다.” 정도를 찾을 수 있습니다.
즉 데이터를 사용자가 질의(query) 를 통해 필요에 맞게 요청하고, 전달받을 수 있게 돕습니다. 그리고 그 과정에서 type 을 사용해 보다 정확한 의사소통을 가능하게 합니다.
GraphQL 서비스는 타입과, 필드 그리고 각 타입과 필드에 대한 함수로 구현합니다. 즉 type definition 을 하고, resolver 통해 데이터를 꺼내옵니다.
다음은 로그인한 유저의 정보를 가져오는 간단한 예입니다.
type Query {
me: User
}
type User {
id: ID
name: String
}
각 필드에 대한 함수를 작성하면 다음과 같습니다.
function Query_me(request) {
return request.auth.user;
}
function User_name(user) {
return user.getName();
}
GraphQL 서비스가 실행되면 (일반적으로는 웹 서비스의 URL) GraphQL 쿼리를 전송하여 유효성을 검사하고 실행할 수 있습니다. 수신된 쿼리는 먼저 정의된 타입과 필드만 참조하도록 검사한 다음, 함수를 실행하여 결과를 생성합니다.
아래는 쿼리 예제입니다.
{
me {
name
}
}
다음과 같은 JSON을 얻게됩니다.
{
"me": {
"name": "Luke Skywalker"
}
}
[Queries and Mutations]
- Fields: GraphQL 은 객체에 대한 특정 필드를 요청하는 것이 간단할 뿐 아니라 쿼리와 결과의 구조가 같기에 보다 명확하게 정보를 요청하고 얻을 수 있다.
가장 간단한 형태로는 다음과 같이 구현할 수 있다.
{
hero {
name
}
}
그리고 다음과 같이 보다 깊은 구조, 또는 String 타입이 아닌 객체를 요청할 수도 있다.
{
hero {
name
# 쿼리에 주석을 쓸 수도 있습니다!
friends {
name
}
}
}
- Arguments: 필드에 인자를 추가해서 보다 다양한 조건으로 가져올 조건을 작성할 수 있다.
- Aliases: 결과를 원하는 이름으로 리네임하여 사용할 수 있다. 이를 통해 정보를 요청하고 가져오는데에 자유도가 증가한다.
- Fragments: 복잡하거나 중복된 요청이 필요한 경우 사용하면 좋다. 프래그먼트는 재사용 가능하며, 복잡한 데이터 요청을 작은 단위로 분할하는데 사용한다.
Fragments 안에서 변수 사용하기.
query HeroComparison($first: Int = 3) {
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}fragment comparisonFields on Character {
name
friendsConnection(first: $first) {
totalCount
edges {
node {
name
}
}
}
}
- Operation Name: 작업타입은 query, mutation, subscription 이 있다. 작업시 명확히 명시하여 작성하는게 좋다. 작업이름은 HeroNameAndFriends 로써, 해당 작업에 맞게 이름을 작성하여 사용한다. 함수에 이름을 붙이듯이 디버깅, 로깅하기에 편리한 장점이 있다.
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
- Variables: 프로그램에서 클라이언트 측 코드와 동적으로 대응해야 할 부분을 쿼리 문자열에서 빼내 별도로 전달하며 이러한 값을 변수라고 한다.
변수를 사용하기 위해서는 다음 세 가지 작업을 해야 합니다.
- 쿼리안의 정적 값을
$variableName
으로 변경합니다. $variableName
을 쿼리에서 받는 변수로 선언합니다.- 별도의 전송규약(일반적으로는 JSON) 변수에
variableName: value
을 전달하세요.
다음과 같은 형태를 띄게됩니다.
- react 에서 props 를 사용하는 것과 유사한 것 같다.
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}{
"episode": "JEDI"
}
변수 정의:
변수 정의는 위 쿼리에서 ($episode: Episode)
부분입니다. 정적타입 언어의 함수에 대한 인자 정의와 동일합니다. $
접두사가 붙은 모든 변수를 나열하고 그 뒤에 타입(이 경우 Episode
)이 옵니다.
선언된 모든 변수는 스칼라
, 열거형
, input object type
이어야 합니다. 복잡한 객체를 필드에 전달하려면 서버에서 일치하는 입력 타입을 알아야합니다. 스키마 페이지에서 input object type
에 대해 자세히 알아보세요.
변수 정의는 옵셔널이거나 필수일 수 있습니다. 위의 경우 Episode
타입 옆에 !
가 없으므로 옵셔널입니다. 그러나 변수를 전달할 필드에 null이 아닌 인자가 요구된다면 변수가 필요하게 됩니다.
이러한 변수 정의 문법에 대한 자세한 내용을 보려면 GraphQL 스키마 언어를 익히는 것이 좋습니다. 스키마 언어는 스키마 페이지에서 자세히 설명합니다.
변수 기본값:
타입 선언 다음에 기본값을 명시하여 쿼리의 변수에 기본값을 할당할 수도 있습니다.
- Directives: 조건문을 건다고 생각하면 쉽다.
@include(if: Boolean)
: 인자가true
인 경우에만 이 필드를 결과에 포함합니다.@skip(if: Boolean)
인자가true
이면 이 필드를 건너뜁니다. - Mutations: 서버 측 데이터를 수정할 때 사용한다. 사용 방법은 query 를 쓸 때와 거의 비슷하다. query 와의 다른점은 쿼리필드는 병렬로 실행되지만 뮤테이션 필드는 하나씩 차례대로 실행된다는 것이며 이것은 하나의 요청에서 두개의 뮤테이션을 보내면 첫번째 요청은 두번째 요청 전에 완료되는 것이 보장된다는 것이다.
- Inline Fragments
- Meta fields: GraphQL 서비스에서 리턴될 타입을 모르는 상황이 발생하면 클라이언트에서 해당 데이터를 처리하는 방법을 결정할 방법이 필요합니다. GraphQL을 사용하면 쿼리의 어느 지점에서나 메타 필드인
__typename
을 요청하여 그 시점에서 객체 타입의 이름을 얻을 수 있습니다.
아직 사용해보지 않았지만 꽤 유용한 기능 같다. 문서 확인!