코드스테이츠_S4U6_3W_목

윤뿔소·2022년 12월 1일
0

CodeStates

목록 보기
41/47

오늘은 요즘 많이 급부상한 API 개발 쿼리 언어 GraphQL에 대해 공부해보자!

GraphQL

왜 사용할까?

이름 그대로 인간 뇌 구조 및 언어적인 설명과 비슷한 '그래프' 자료구조를 생각해 현실 세계의 많은 현상들을 모델링할 수 있는 강력한 도구로 쓸 수 있기 때문이다! 마인드맵과 유사한 데이터 구조를 가져서 익숙하기도 하다!
그래프로 이루어진 GraphQL은 모든 데이터가 그래프 형태로 연결되어 있다고 전제하지만 '클라이언트가 어떤 데이터를 필요로 하느냐'에 따라 유연하게 트리 구조의 JSON 데이터(GraphQL을 사용해 트리를 추출)로 응답할 수 있는 특장점이 있다.
즉! REST API 방식의 고정된 자원이 아닌 클라이언트 요청에 따라 유연하게 자원을 가져올 수 있다는 점에서 엄청난 이점이 있다!

특장점

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

장점

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

단점

  • REST API에 친숙한 개발자의 경우 GraphQL를 학습하는 데 시간이 필요.
  • 캐싱이 REST보다 훨씬 복잡.
    • HTTP에선 각 메소드에 따라 캐싱이 구현되어 있음. 하지만 GraphQL에선 POST 메소드만을 이용해 요청을 보내기 때문에 각 메소드에 따른 캐싱을 지원받을 수 없음!
      그래서 이를 보안하기 위해 Apollo 엔진의 캐싱과 영속 쿼리 등이 등장
  • 고정된 요청과 응답만 필요할 경우에는 Query 로 인해 요청의 크기가 RESTful API 의 경우보다 더 커짐.

REST API vs 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를 호출하여 작업을 처리.

즉! REST API의 단점인 Overfetching, Underfetching 문제를 해결할 수 있다!!

  • Underfetch: endpoint 가 필요한 정보를 충분히 제공하지 못함
    • Underfetch의 경우 클라이언트는 필요한 정보를 모두 확보하기 위하여 추가적인 요청을 보내야만 함. 어떤 블로그 화면을 구현하기 위해선 유저 정보 뿐만 아니라 유저의 포스팅 목록 및 유저가 보유한 팔로워까지 필요. 이때 필요한 정보를 모두 가져오려면 REST API에서는 각각의 자원에 따라 엔드포인트를 구분하기 때문에 3가지 엔드포인트에 요청을 보내야함. ㅠㅠ
  • 클라이언트 구조 변경 시 엔드포인트 변경 또는 데이터 수정이 필요함
    • REST API에서는 자원의 크기와 형태를 서버에서 결정하기 때문에 클라이언트가 직접 데이터의 형태를 결정 불가. 이로 인해 만약 클라이언트에서 필요한 데이터의 내용이 변할 경우 다른 endpoint를 통해 변경된 데이터를 가져오거나 수정을 해야함.

사용법

REST API에는 GET 요청 ? GraphQL에서는 Query를 이용해 원하는 데이터를 요청 / Create, Delete와 같이 저장된 데이터를 수정 ? Mutation을 이용해 이를 수행

GraphQL에서는 더 나아가 구독(Subscription)이라는 개념을 제공하며 이를 이용해 실시간 업데이트를 구현

GraphQL 구조 및 용어

  • Query: 저장된 데이터 가져오기 (REST의 GET과 비슷합니다.)
  • Mutation: 저장된 데이터 수정하기
    • Create: 새로운 데이터 생성
    • Update: 기존의 데이터 수정
    • Delete: 기존의 데이터 삭제
  • Subscription: 특정 이벤트가 발생 시 서버가 대응하는 데이터를 실시간으로 클라이언트에게 전송

공식사이트로 가서 Fields, Arguments, Aliases(별명), Operation Name, Variables, Directives, Mutations(POST), Schema, Resolver 등 공식사이트를 봐서 꼭 구조에 알고 어떻게 동작하는지 알자!
GraphQL의 가장 특이한 부분만 기술하겠다.

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

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

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

스키마/타입(Schema/Type)

서비스에서 가져올 수 있는 객체의 종류, 그리고 포함하는 필드를 나타내는 객체 유형

type Character {
  name: String!
  appearsIn: [Episode!]!
}
  • Character는 GraphQL 객체 타입이며, 즉 필드가 있는 타입임을 의미합니다. 스키마에 있는 대부분의 타입은 객체 타입입니다.
  • name 과 appearIn 은 Character 타입의 필드 입니다. 즉 name 과 appearIn 은 GraphQL 쿼리의 Character 타입 어디서든 사용할 수 있는 필드입니다.
  • String은 내장된 스칼라 타입 중 하나입니다. 이는 단일 스칼라 객체로 확인되는 유형이며 쿼리에서 하위 선택을 가질 수 없습니다. 스칼라 타입에는 ID, Int도 있습니다.
  • !가 붙는다면 이 필드는 nullable하지 않고 반드시 값이 들어온다는 의미입니다. 이것을 붙여 쿼리한다면 반드시 값을 받을 수 있을 것이란 예상을 할 수 있습니다.
  • [ ]는 배열을 의미합니다. 배열에도 !가 붙을 수 있습니다. 여기서는 ! 이 뒤에 붙어 있어 null 값을 허용하지 않으므로 항상 0개 이상의 요소를 포함한 배열을 기대할 수 있게 됩니다.

리졸버(Resolver)

위와 같이 스키마를 정의하면 그 스키마 필드에 사용되는 함수의 실제 행동을 Resolver에서 정의

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

GraphQL에서는 데이터를 가져오는 구체적인 과정을 직접 구현해야 하는데 이와 같은 작업(e.g. 데이터베이스 쿼리, 원격 API 요청)을 Resolver가 담당

실습

깃헙 GraphQL API로 직접 작성하거나 explore로 실습해보고, 깃헙 GraphQL API의 구조를 알아서 쿼리를 작성해 live data를 가져와보자!
문제는 내 깃헙저장소에!

import "./App.css";
import { graphql } from "@octokit/graphql";
import { useState, useEffect } from "react";
import styled from "styled-components";
// require("dotenv").config();

// const GITHUB_TOKEN_ENV = process.env.GITHUB_TOKEN_ENV;
// console.log(GITHUB_TOKEN_ENV);

const Loading = styled.div`
  // 로딩컴포넌트 스타일
`;

const getRepository = async () => {
  const { repository } = await graphql(
    `
      {
        repository(name: "agora-states-fe", owner: "codestates-seb") {
          discussions(first: 100) {
            edges {
              node {
                id
                title
                createdAt
                url
                author {
                  login
                  avatarUrl
                }
                category {
                  name
                }
                answer {
                  author {
                    login
                  }
                }
              }
            }
          }
        }
      }
    `,
    {
      headers: {
        authorization: `token ${/* 깃헙 토큰 입력 */}`,
      },
    }
  );
  return repository;
};

function App() {
  const [agoraData, setAgoraData] = useState({});
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    getRepository()
      .then((res) => {
        setAgoraData(res.discussions.edges);
        setIsLoading(false);
      })
      .catch((error) => console.log(Error, error));
  }, []);

  return (
    <div className="App">
      {isLoading ? (
        <Loading>
          {/* ~로딩컴포넌트 */}
        </Loading>
      ) : (
        <ul>
          {agoraData.map((edge) => {
            return (
              <li key={edge.node.id}>
                <img src={edge.node.author.avatarUrl} alt={`avatar of ${edge.node.author.login}`} />
                <div>{`[${edge.node.category.name}]`}</div>
                <a href={edge.node.url}>{edge.node.title}</a>
                <p>{edge.node.answer ? "☑" : "☒"}</p>
              </li>
            );
          })}
        </ul>
      )}
    </div>
  );
}

export default App;
  1. npm 깔고 import해오기
  2. 아고라스테이츠 레포 속 discussions의 데이터를 가져오려고 쿼리를 작성(깃헙 API로 explore로 찾아보고 그대로 복사해옴 ㅎㅎ)
  3. 상태와 useEffect를 작성하여 페이지를 열면 렌더링돼 set 함수에 추출한 정보 담아주기
  4. Loading 컴포넌트와 isLoading으로 로딩 전까지 있다가 렌더링 되면 true로 바뀜
  5. 가져온 데이터를 map으로 뿌리기!

추가: 12월

viewer도 나오게끔 기능 추가하고 이거 리팩토링했음!! 컴포넌트로 좀 쪼개고, styled-component 오랜만에 복습 겸 써봤음 ㅎㅎ
더 쪼갤 수 있음: GraphQL을 쓰는 getRepository 함수를 따로 JS파일 만들어서 리턴한 데이터를 props로 내려주는 등

알게된 것

이걸로 서버를 더 쉽게 조작할 수 있게 됐다! 물론 만드는 건 아직 모르지만 REST API로 조작하고 만들 때 생각하면 진짜 선녀다.. 점점 점유율이 높아지니 배워놔야겠다.

profile
코뿔소처럼 저돌적으로

0개의 댓글