[GraphQL] GraphQL GraphQL의 구성요소

GraphQL에 대한 고찰 - 2

Posted by owin2828 on 2020-11-13 16:49 · 6 mins read

들어가기 앞서

저번 포스팅에 이어, GraphQL에 대해 알아보고자 한다. 실제 구성요소구현 방법등에 대하여 서술할 예정이다.

1. GraphQL의 구성요소


GraphQL는 크게 다음과 같은 구성 요소로 이루어져 있다.

  1. 쿼리/뮤테이션(query/mutation)
  2. 스키마/타입(schema/type)
  3. 리졸버(resolver)

1-1. 쿼리/뮤테이션(query/mutation)

GraphQL에서는 요청을 보내는 방법을 2가지로 정의하는데, 쿼리뮤테이션이다. 이 둘은 다른것 같지만, 실상은 별 차이가 없다.
쿼리조회(R)에 사용되고, 뮤테이션은 데이터의 변조(CUD)에 사용되는 개념적인 규약이다.

// 쿼리를 통한 데이터 조회

// 단순한 조회 작업
{
  human(id: "1000") {
    name
    height
  }
}

// 인자를 통한 조회
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
// mutation을 통한 데이터 변조
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}


1-2. 스키마/타입(schema/type)

카카오 기술블로그에는 다음과 같은 설명이 첨부되어 있다.

데이터베이스 스키마(Schema)를 작성할 때의 경험을 SQL 쿼리 작성으로 비유한다면, gql 스키마를 작성할 때의 경험은 C, C++의 헤더파일 작성에 비유가 됩니다.
그러므로, 프로그래밍언어(C, C++, JAVA등)에 익숙한 프로그래머라면 스키마 정의 또한 쉽게 배우실 것입니다.

즉, 스키마라는 것은 GraphQL에서 사용될 객체 타입을 사전에 지정하는 작업으로써 다음과 같은 형식을 지닌다.

type Character {
  name: String!
  appearsIn: [Episode]!
}
  • 오브젝트 타입 : Character
  • 필드 : name, appearsIn
  • 스칼라 타입 : String, ID, Int 등
  • 느낌표(!) : 필수 값을 의미(non-nullable)
  • 대괄호([, ]) : 배열을 의미(array)

1-3. 리졸버(Resolver)

데이터베이스에는 데이터베이스 어플리케이션을 사용하여 데이터를 가져오는 구체적인 과정이 구현되어 있다.
그러나 GraphQL에서는 데이터를 가져오는 구제적인 과정을 직접 구현해야 하며 이는 리졸버(Resolver)가 담당하게 된다.
이를 통해 데이터베이스뿐 아니라, 일반 파일 및 http SOAP 같은 네트워크 프로토콜을 활용하여 원격 데이터를 가져올 수 있다.

GraphQL에서는 각 필드마다 하나의 함수가 존재하게 되고, 이 함수는 다음 타입을 반환하게 되며 이 각 함수를 리졸버라 부른다.
필드가 스칼라 값(String, Int 같은 primtive 타입)인 경우에는 연쇄 호출이 중지되고, 종료된다.

type Query {
  users: [User]
  user(id: ID): User
  limits: [Limit]
  limit(UserId: ID): Limit
  paymentsByUser(userId: ID): [Payment]
}

type User {
    ...
}

type Limit {
    ...
    user: User
    ...
}

type Payment {
    ...
	user: User!
    ...
}

위와 같은 코드에서 User와 Limit는 1:1, User와 Payment는 1:N의 관계이다.

이때 다음과 같은 동일한 쿼리명을 가진 2가지 쿼리를 살펴보자.

// 쿼리1. 필드값 2개
{
  paymentsByUser(userId: 10) {
    id
    amount
  }
}
// 쿼리2. 필드값 3개
{
  paymentsByUser(userId: 10) {
    id
    amount
    user {
      name
      phoneNumber
    }
  }
}

이 때 필드 1개당 리졸버 함수 1개가 불리게 되므로, 밑의 쿼리가 더 많은 함수를 호출하게 된다.
또한 각각의 리졸버 함수는 내부적으로 데이터베이스 쿼리가 존재하게 되는데, 이 두가지를 조합하면 다음과 같은 사실을 알 수 있다.

  • 쿼리에 맞게 필요한만큼만 최적화하여 호출 가능

즉 기존의 RESTful API는 정해진 쿼리가 무조건 호출됨에 비해, 리졸버 체인을 잘 활용하여 효율적인 설계를 할 수 있다는 뜻이다.

2. GraphQL 비지니스 로직


비지니스 로직 레이어(출처: https://graphql.github.io/learn/thinking-in-graphs/)

GraphQL을 이용하여 비지니스 로직을 작성할 때, 실제 로직은 리졸버 함수에 담지 않는다.
유효성 검사 및 권한 확인과 실제 로직은 전부 전용 비지니스 로직 레이어 내부에 담게 된다.

requestPaymentSession: async (parent, { 
    pgId, name, sex, birthDay, phoneNumber, amount, productName, ref 
}, context, info) => {
    // 실제 로직은 비지니스 레이어로
    const ret = await requestPaymentSession({ pgId, name, birthDay, phoneNumber, sex, amount, productName, ref })
    return removeSymbol(ret)
},

requestPaymentApprove: async (parent, {
    sessionKey, authNumber
}, context, info) => {
    // 실제 로직은 비지니스 레이어로
    const ret = await requestApprovePayment({ sessionKey, authNumber })
    return removeSymbol(ret)
}


끝마치며


이로써 기본적인 GraphQL의 구성구현 방법에 대하여 알아보았다.
물론 실제로 코드에 위의 개념을 녹이는 것은 완전 다른 이야기지만..

Refernece