[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