GraphQL의 이해

Cramming An·2022년 4월 4일
0

FrontEnd

목록 보기
9/9
post-thumbnail

네이버 인턴쉽을 진행하면서 GraphQL을 상당히 많이 사용했다. React 기반의 view에 필요한 데이터는 GraphQL + Apollo 조합으로 모두 호출했다. 하지만 활용에 비해 근본적인 이해를 하고 있는지 의심이 갔고, 이번 기회에 정리해볼까 한다.

GraphQL 이란

graphql은 한 마디로 웹 클라이언트가 데이터를 서버로 부터 효율적으로 가져오기 위해 만들어진 쿼리언어 입니다.

따라서 클라이언트에서 gql로 작성된 쿼리를 서버사이드로 넘기면, 서버는 쿼리를 입력으로 받고 처리한 후, 결과를 다시 클라이언트에게 넘기게 됩니다.

Rest API와의 비교

사실 양쪽 모두 존재 목적은 비슷합니다. 클라이언트에서 데이터를 요청하면, 서버에서 응답하고, 데이터를 넘겨주는 형식입니다.

하지만 명확한 차이가 있습니다.

  • 엔드포인트: Rest API는 url과 method의 조합으로 다양한 엔드포인트가 존재합니다. 반면 gql은 스키마의 타입마다 쿼리가 달라지기 때문에 하나의 엔드포인트를 가집니다.

GraphQL의 쿼리

여기서 하나의 엔드포인트의 의미에 대해 알아봅시다.
클라이언트에서 필요한 데이터를 gql 이란 언어로 쿼리문을 작성해 서버사이드에 보내는 것까지는 알게되었습니다. 그럼 어떤 방식으로 쿼리문을 작성할까요? 예시를 보겠습니다.

query getStudentInfomation($studentId: ID){
  personalInfo(studentId: $studentId) {
    name
    address1
    address2
    major
  }
  classInfo(year: 2018, studentId: $studentId) {
    classCode
    className
    teacher {
      name
      major
    }
    classRoom {
      id
      maintainer {
        name
      }
    }
  }
  SATInfo(schoolCode: 0412, studentId: $studentId) {
    totalScore
    dueDate
  }
}

Rest API와 같이 studentId라는 클라이언트 사이드에서 만든 input 데이터를 넘겨 그에 맞는 데이터를 요청하고 있습니다. 하지만 특이한 점은 요청하는 데이터의 종류가 여러가지 입니다. personalInfo,classInfo,SATInfo 이 세가지 데이터 정보를 한 번에 요청하고 있습니다. Rest API 같으면 /personalInfo?studentId=123,/classInfo?studentId=123, ... 이렇게 3번에 걸쳐 요청해야겠지만, gql은 하나의 엔드포인드에 필요한 여러 종류의 데이터를 요청해 효율적인 호출이 가능합니다.

이렇게 여러 쿼리를 함수를 이용해 한 번에 처리하는 방식을 오퍼레이션 네임 쿼리라고 합니다.

이런 변화는 프론트엔드 개발자로 하여금, 백엔드의 전통적인 API 요청/응답 방법에 대한 의존성을 덜어내주고, 프론트엔드에 필요한 데이터 요청/응답 방식을 프론트엔드 개발자가 직접 커스터마이징하여 효율적인 자원으로 일을 진행하게 될 수 있습니다.

받은 쿼리문 처리하기

클라이언트에서 서버에서 정한 스키마에 맞추어 자유롭게 쿼리문을 작성하고, 성공적으로 서버에 도착했습니다. 이때, 서버에서는 이 쿼리문을 어떻게 처리할까요?

node.js와 mongoDB 환경기준

  • 우선 받은 쿼리문을 파싱해야 합니다. 하지만 다행히 gql라이브러리가 파싱을 도와줍니다.
  • 이 후, 각각의 쿼리문이 주어진 input 을 가지고, 스키마에서 보여준 데이터를 리턴하도록 함수를 서버에서 작성해야 합니다. 이때 이 함수를 리졸버라고 부릅니다.

    참고로 리졸버의 리턴값을 다른 리졸버의 출력 데이터 중 하나로 활용될 수 있습니다. 따라서 같은 종류의 데이터를 호출할 때는 이 방식으로 최적화가 가능합니다.

// user(id: ID)가 paymentsByUser(userId: ID) 안에서 호출될 수 있음
type Query {
  users: [User]
  user(id: ID): User
  limits: [Limit]
  limit(UserId: ID): Limit
  paymentsByUser(userId: ID): [Payment]
}

type Payment {
	id: ID!
	limit: Limit!
	user: User!
	pg: PaymentGateway!
	productName: String!
	amount: Int!
	ref: String
	createdAt: String!
	updatedAt: String!
}

리졸버는 DB와 소통하며 스키마의 출력 데이터 타입에 맞추어, 요청받은 데이터를 DB로 부터 가져오거나, 혹은 수정(생성, 업데이트, 삭제)을 하는 역할을 수행해야 합니다.

예시를 보겠습니다.

export const getFood = async (_, { getFoodInput: { foodID: _id } }) => {
  try {
    const foodList = await Trip.find({ _id })
    if (!(foodList?.length > 0)) return null
    return foodList
  } catch (err) {
    throw new Error(err)
  }
}

클라이언트로부터 받은 foodIDmongoDBfoodList를 찾고 데이터를 리턴합니다. 이 때, mongoose를 활용하므로 비동기 함수가 됩니다.

이렇게 직접적으로 DB에 요청할 수 있지만, http를 이용해 데이터를 fetch하거나 혹은 다양한 방법으로 데이터를 가져올 수 있습니다. 이러한 리졸버의 확장성은 legacy 코드의 마이그레이션을 도울 수 있습니다.

서버-클라이언트 협업

클라이언트에서는 Rest API와 달리 API 명세서 없이, 스키마 만 있으면 원하는 데이터에 대한 쿼리문 작성이 가능합니다.

이때 이 스마카를 보기 좋게 정리해둔 쿼리용 IDE가 있습니다.

playground (apollo server 라이브러리)

클라이언트에서 GraphQL 사용법 (React 환경)

클라이언트에서 gql을 사용한다는 의미는 다음과 같습니다.

  • 쿼리문 작성
  • 쿼리문 서버로 전송, 응답받기
  • 변경된 데이터 React 컴포넌트 내에서 상태관리

쿼리문 작성

playground 등을 통해 스키마를 보고, 필요한 데이터를 조합해 쿼리문을 작성합니다.

쿼리문 서버로 전송, 응답받기

Rest API 와 마찬가지로 서버와는 http로 통신합니다. Fetch함수를 통해 쿼리문을 전달할 수도 있지만, 좀 더 직관적이고 추상화가 잘 된 라이브러리가 있습니다.

Apollo Client 입니다.
gql을 사용해 쿼리한 데이터 상태관리 라이브러리 입니다.

Apollo client의 hook을 이용한 함수 하나로 클라이언트에서 서버로 데이터 쿼리를 요청할 수 있습니다.

const { loading, error, data } = useQuery(GET_DOGS);

이때, 쿼리한 데이터는 반드시 Apollo Cache라는 storage에 들어가게 되는데, 다음 쿼리 발생 시 Apollo Cache를 먼저 탐색함으로써 불필요한 서버의 호출을 방지합니다.

변경된 데이터 React 컴포넌트 내에서 상태관리

Apollo client의 hook은 React hook이기 때문에 쿼리를 할 때, 값이 바뀌면 리렌더링을 통해 즉각적인 UI 변화가 일어납니다.

reference

profile
La Dolce Vita🥂

0개의 댓글