코드스테이츠 15주차[FE 41기]

이동국·2022년 12월 6일
0

Unit6 - [API] GraphQL

GraphQL에 대해 배우게 되었다.
GraphQL은 세상에 나온지 얼마 안 돼서 API 개발의 선두로 올라온 언어라고 한다.
나한테는 처음 보는 개념이라 솔직히 이게 뭔지 잘 몰랐지만, 배우고나니 꽤 편리한 기능을 가지고 있다고 생각을 하게 되었다.

이번 유닛에서는 지난 섹션에서 배웠던 개념 중 REST API와 GraphQL의 차이 및 GraphQL을 다루는 법에 대해 학습하였고, GraphQL을 잘 배워두면 API 개발을 하는 데에 있어서 더 폭 넓은 선택을 할 수 있도록 도와줄 것 같다!

GraphQL

GraphQL란?

GraphQL은 Graph + Query Language의 줄임말로 페이스북에서 만든 쿼리 언어이고, 2016년 처음으로 등장해 현재까지 인지도 및 만족 부분에서 높은 비율을 차지하고 있다고 한다.
쉽게 말해 API를 위한 쿼리 언어라고 할 수 있다.

왜 Graph를 사용할까?

GraphQL의 아이디어는 그래프로 생각하기에서부터 출발하는데, 그래프라는 자료구조는 인간의 뇌 구조 및 언어적인 설명과 비슷하기 때문에 실제 현실 세계의 많은 현상들을 모델링할 수 있는 강력한 도구이다. 따라서 그래프 자료구조를 살펴보면 우리가 특정 개념을 학습하고 이를 다른 개념과 연관시킬 때 자연스럽게 사용하는 마인드맵과 유사한 데이터 구조를 가진다는 것을 알 수 있다.

GraphQL에서는 모든 데이터가 그래프 형태로 연결되어 있다고 전제하는데, 일대일로 연결된 관계도, 여러 계층으로 이루어진 관계도 모두 그래프이다. 트리나 그래프나 노드와 노드를 연결하는 간선으로 구성된 자료구조이기 때문이다. 단지 그 그래프를 누구의 입장에서 정렬하느냐에 따라 트리 구조를 이룰 수 있다.

이를 통해 GraphQL은 클라이언트 요청에 따라 유연하게 트리 구조의 JSON 데이터를 응답으로 전송할 수 있는데, 다시 말해 GraphQL은 REST API 방식의 고정된 자원이 아닌 클라이언트 요청에 따라 유연하게 자원을 가져올 수 있다는 점이 아주 큰 이점을 갖는다.

GraphQL의 특징

  • GraphQL은 HTTP를 통해 API 서버로 요청을 보내고 응답을 받는다.
  • 응답을 받을 시, 데이터 결과를 JSON 형식으로 받는다.
  • GraphQL은 서버 개발자가 작성한 각 필드에 대응하는 resolver 함수로 각 필드의 데이터를 조회할 수 있다.
  • GraphQL은 GraphQL 라이브러리가 조회 대상 schema가 유효한지 검사한다.

GraphQL vs REST API

REST API의 한계

  • Overfetch : 필요 없는 데이터까지 제공

예를 들어 유저의 이름만 필요한 상황에서 REST API를 사용한다면, 응답 데이터에는 유저의 주소, 생일 등과 같이 실제로는 클라이언트에게 필요없는 정보가 포함되어 있을 수도 있다.

  • Underfetch : endpoint 가 필요한 정보를 충분히 제공하지 못함

REST API에서는 자원의 크기와 형태를 서버에서 결정하기 때문에 클라이언트가 직접 데이터의 형태를 결정할 수 없다고 한다. 예를 들어 클라이언트에서 필요한 데이터의 내용이 변할 경우 다른 endpoint를 통해 변경된 데이터를 가져오거나 수정을 해야 한다.

REST API와 GraphQL의 다른점

  • REST API는 Resource에 대한 형태 정의와 데이터 요청 방법이 연결되어 있지만, GraphQL에서는 Resource에 대한 형태 정의와 데이터 요청이 완전히 분리되어 있다.
  • REST API는 Resource의 크기와 형태를 서버에서 결정하지만, GraphQL에서는 Resource에 대한 정보만 정의하고, 필요한 크기와 형태는 클라이언트 단에서 요청 시 결정한다.
  • REST API는 URI가 Resource를 나타내고 Method가 작업의 유형을 나타내지만, GraphQL에서는 GraphQL Schema가 Resource를 나타내고 Query, Mutation 타입이 작업의 유형을 나타낸다.
  • REST API는 여러 Resource에 접근하고자 할 때 여러 번의 요청이 필요하지만, GraphQL에서는 한번의 요청에서 여러 Resource에 접근할 수 있다.
  • REST API에서 각 요청은 해당 엔드포인트에 정의된 핸들링 함수를 호출하여 작업을 처리하지만, GraphQL에서는 요청 받은 각 필드에 대한 resolver를 호출하여 작업을 처리한다.

GraphQL의 장단점

장점

  • 하나의 endpoint 요청
    /graphql이라는 하나의 endpoint 로 요청을 받고 그 요청에 따라 query , mutation을 resolver 함수로 전달해서 요청에 응답한다. 모든 클라이언트 요청은 POST 메소드를 사용한다.
  • No! under & overfetching
    여러 개의 endpoint 요청을 할 필요없이 하나의 endpoint에서 쿼리를 이용해 원하는 데이터를 정확하게 API에 요청하고 응답으로 받을 수 있다.
  • 강력한 playground
    graphql 서버를 실행하면 playground라는 GUI를 이용해 resolver 와 schema 를 한 눈에 보고 테스트 해 볼 수 있다.
  • 클라이언트 구조 변경에도 지장이 없음
    클라이언트 구조가 바뀌어도 필요한 데이터를 결정하고 받는 주체가 클라이언트이기 때문에 서버에 지장이 없다.

단점

  • REST API에 친숙한 개발자의 경우 GraphQL를 학습하는 데 시간이 필요하다.
  • 캐싱이 REST보다 훨씬 복잡하다.
  • 고정된 요청과 응답만 필요할 경우에는 Query 로 인해 요청의 크기가 RESTful API 의 경우보다 더 커진다.

GraphQL 구조

이제 GraphQL 구조에 대해 자세히 알아보자.

쿼리(query, 데이터 조회)

필드(field)

{
  hero {
    name        
  }
}
{
  "data": {
    "hero": {
      "name": "king"
    }
  }
}

hero의 name을 쿼리를 실핼 했을 때이다.
필드의 name은 String 타입을 반환하고, 쿼리와 결과가 정확하게 같은 모양을 하고 있음을 확인할 수 있다.

{
  hero {
    name
    # 이런 식으로 GraphQL 내에서 주석도 작성 가능.
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "king",
      "friends": [
        {
          "name": "queen"
        },
        {
          "name": "baby"
        }
      ]
    }
  }
}

히어로의 이름과 히어로의 친구의 이름이 조회가능하다. 이런 식으로 원하는 필드를 중첩하여 쿼리하는 것도 가능하다는 것이다.

전달인자(Arguments)

필드에 인수를 전달하는 부분을 추가하게 되면 쿼리의 필드 및 중첩된 객체들에 전달하여 원하는 데이터만 받아올 수 있다.

{
  human(id: "77") {
    name
    height
  }
}
{
  "data": {
    "human": {
      "name": "zozo",
      "height": 1.77
    }
  }
}

id가 1000인 human의 name과 height를 쿼리를 하였다.
이런 식으로 id가 77 human의 이름과 키를 쿼리해 올 수 있었다.

별명(Aliases)

필드 이름을 중복해서 사용할 수 없으므로, 필드 이름을 중복으로 사용해서 쿼리를 해야 할 때는 별명을 붙여서 쿼리해야 한다.

{
  hero(episode: king) {
    name
  }
  hero(episode: queen) {
    name
  }
}

이런식으로 불가능하다.

{
  kingHero: hero(episode: king) {
    name
  }
  queenHero: hero(episode: queen) {
    name
  }
}

앞에 알아볼 수 있는 별명을 붙여주면 쿼리할 수 있다.

{
  "data": {
    "kingHero": {
      "name": "zozo"
    },
    "queenHero": {
      "name": "mimi"
    }
  }
}

위와 같이 다른 이름으로 별명을 지정하면 한 번의 요청으로 두 개의 결과를 모두 얻어낼 수 있다.

오퍼레이션 네임(Operation name)

실제 앱에서는 코드를 모호하지 않게 작성하는 것이 중요하다고 한다.

query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "king",
      "friends": [
        {
          "name": "queen"
        },
        {
          "name": "baby"
        }
      ]
    }
  }
}

앞의 query는 오퍼레이션 타입으로, 오퍼레이션 타입에는 query 뿐만 아니라 mutation, subscription, describes 등이 있다.
쿼리를 약식으로 작성하지 않는 한 이런 오퍼레이션 타입은 반드시 필요하다.

변수(Variables)

여태껏 고정된 인수를 받아 쿼리했지만, 실제 앱을 사용할 때는 고정된 인수를 받는 것보다는 동적으로 인수를 받아 쿼리하는 경우가 대다수이다.

query HeroNameAndFriends($episode: king) {
  hero(episode: $king) {
    name
    friends {
      name
    }
  }
}

오퍼레이션 네임 옆에 변수를 $변수 이름: 타입 형태 로 정의하면 되고, 뒤에 !가 붙는다면 episode는 반드시 Episode여야 한다는 뜻이다.

뮤테이션(mutation, 데이터 수정)

GraphQL은 대개 데이터를 가져오는 데에 중점을 두고 있지만 서버측 데이터를 수정하기도 한다.

mutation CreateReviewForEpisode($ep: king!, $review: ReviewInput!) {
  createReview(king: $ep, review: $review) {
    stars
    commentary
  }
}

스키마/타입(Schema/Type)

GraphQL 스키마의 가장 기본적인 구성 요소는 서비스에서 가져올 수 있는 객체의 종류, 그리고 포함하는 필드를 나타내는 객체 유형이다.

type Character {
  name: String!
  appearsIn: [king!]!
}

배열에도 !가 붙을 수 있다. 여기서는 ! 이 뒤에 붙어 있어 null 값을 허용하지 않으므로 항상 0개 이상의 요소를 포함한 배열을 기대할 수 있게 된다.

리졸버(Resolver)

요청에 대한 응답을 결정해주는 함수로써 GraphQL의 여러 가지 타입 중 Query, Mutation, Subscription과 같은 타입의 실제 일하는 방식 즉 로직을 작성한다.
위와 같이 스키마를 정의하면 그 스키마 필드에 사용되는 함수의 실제 행동을 Resolver에서 정의하고, 또한 이러한 함수들이 모여 있기 때문에 보통 Resolvers라고 부른다.

const db = require("./../db")
const resolvers = {
  Query: { // **Query :** 저장된 데이터 가져오기 
		getUser: async (_, { email, pw }) => {
			db.findOne({
				where: { email, pw }
			}) ... // 실제 디비에서 데이터를 가져오는 로직을 작성
			...
		}
  },
  Mutation: { // **Mutation :** 저장된 데이터 수정
		createUser: async (_, { email, pw, name }) => {
			...
		}
  }
  Subscription: { // **Subscription :** 실시간 업데이트
    newUser: async () => {
      ...
		}
  }
};

0개의 댓글