GraphQL

uxolrv·2022년 10월 5일
0

📌 GraphQL

Graph + Query Language
Query Language 중에서도 Server API를 통해 정보를 주고받기 위해 사용하는 Query Language
➡️ API를 위한 쿼리 언어!




💡 Graph를 사용하는 이유

그래프: 여러 개의 점들이 서로 복잡하게 연결되어 있는 관계를 표현한 자료구조
Node, 정점(vertex): 그래프 상의 하나의 점
간선(edge): 그래프 상의 하나의 선


그래프 자료구조는 인간의 뇌 구조 및 언어적인 설명과 비슷하여 현실세계의 많은 현상들을 모델링할 수 있는 강력한 도구 (마인드맵과 유사한 데이터 구조)

직접적인 관계가 있는 경우 두 노드 사이를 이어주는 간선이 존재하며, 간접적인 관계라면 몇 개의 노드와 간선에 걸쳐 이어진다.

각 노드 간의 간선을 통해 특정한 순서에 따라 그래프를 재귀적으로 탐색할 수 있다.


GraphQL에서는 모든 데이터가 그래프 형태로 연결되어 있다고 전제한다. (트리도, 그래프도 노드와 노드를 연결하는 간선으로 구성된 자료구조)

GraphQL은 클라이언트 요청에 따라 유연하게 트리 구조의 JSON 데이터를 응답으로 전송할 수 있다.
➡️ GraphQL은 REST API 방식의 고정된 자원이 아닌 클라이언트 요청에 따라 유연하게 자원을 가져올 수 있다!




💡 GraphQL로 그래프 순회

데이터들의 관계를 그래프로 표현하면, 데이터의 조각들이나 나타내고자 하는 엔티티 간의 관계를 나타낼 수 있다.
➡️ GraphQL을 사용해 트리 추출 가능

트리는 방향성은 존재하나, 사이클은 존재하지 않는 비순환 그래프

엔티티: 사물의 구조나 상태, 동작 등을 모델로 표현하는 경우, 그 모델의 구성요소
정보의 측면에서 볼 때 속성 자체로는 중요한 의미를 표현하지 못하지만, 속성이 모여 객체를 구성할 때는 큰 의미를 제공한다.
e.g. 학생이라는 객체는 "학번", "학과", "이름"이라는 3개의 속성으로 구성


🔎 그래프에서 트리 추출하는 방법

GraphQL은 하나의 노드에서 시작하여 중첩된 각 필드로 표시된 간선을 따라 그래프를 탐색하기 시작한다.

GraphQL의 중첩된 필드는 그래프의 계층 구조로 표현하는 트리 구조로 표현 가능!
➡️ GraphQL은 트리 구조로 쿼리 결과를 받기 위해 그래프를 탐색하는 쿼리 언어




💡 GraphQL의 특징

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








📌 GraphQL vs REST API

💡 REST API의 한계

  • Overfetch: 필요 없는 데이터까지 제공한다.
    데이터를 요청했을 때, 응답 데이터에 클라이언트에게 필요없는 정보가 포함되어 있을 수도 있다.

  • Underfetch: endpoint가 필요한 정보를 충분히 제공하지 못한다.
    REST API에서는 각각의 자원에 따라 엔드포인트를 구분하기 때문에, 클라이언트는 필요한 정보를 모두 확보하기 위해 추가적인 요청을 보내야 한다.

  • 클라이언트 구조 변경 시 엔드포인트 변경 또는 데이터 수정이 필요하다.
    REST API에서는 자원의 크기와 형태를 서버에서 결정하기 때문에 클라이언트가 직접 데이터의 형태를 결정할 수 없다.
    만약 클라이언트에서 필요한 데이터의 내용이 변할 경우, 다른 endpoint를 통해 변경된 데이터를 가져오거나, 수정해야 한다.




💡 REST API와 GraphQL의 차이

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




👍 GraphQL의 장점

  • 하나의 endpoint로 요청 받는다
    /graphql 이라는 하나의 endpoint로 요청을 받고, 그 요청에 따라 query, mutation을 resolver 함수로 전달하여 요청에 응답한다.
    모든 클라이언트 요청은 POST 메소드 사용!

  • underfetching, overfetching이 일어나지 않는다
    여러 개의 endpoint 요청을 할 필요 없이 하나의 endpoint에서 쿼리를 이용해 원하는 데이터를 정확하게 API에 요청하고 응답으로 받을 수 있다.

  • playground 기능을 사용할 수 있다.
    graphql 서버를 실행하면 playground 라는 GUI를 통해 resolver와 schema를 한 눈에 보고 테스트할 수 있다.

  • 클라이언트 구조 변경에도 지장이 없다.
    클라이언트 구조가 바뀌어도 필요한 데이터를 결정하고 받는 주체가 클라이언트이기 때문에 서버에 지장이 없다.
    클라이언트에서는 어떤 데이터가 필요한 지에 대한 요구사항을 쿼리로 작성하면 된다.




👎 GraphQL의 단점

  • 캐싱이 REST보다 훨씬 복잡하다.
    HTTP에선 각 메소드에 따라 캐싱이 구현되어 있지만, GraphQL에서는 POST만을 이용해 요청을 보내기 때문에 각 메소드에 따른 캐싱을 지원받을 수 없다.
    ➡️ 이를 보완하기 위해 Apollo 엔진의 캐싱, 영속 쿼리 등이 등장!

  • 고정된 요청과 응답만 필요할 경우에는 Query로 인해 요청의 크기가 RESTful API의 경우보다 커진다.








📌 GraphQL 구조

💡 GraphQL Keywords

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

전통적인 Client(요청)-Server(응답) 모델을 따르는 Query, Mutation과 달리, Subscription은 발행/구독(pub/sub) 모델을 따른다.

클라이언트가 어떤 이벤트를 구독하면, 클라이언트는 서버와 WebSocket을 기반으로 지속적인 연결을 형성하고 유지한다.
그 후 특정 이벤트가 발생하면, 서버는 대응하는 데이터를 클라이언트에게 푸시해준다.




💡 쿼리(query, 데이터 조회)

🔎 필드 (field)

  • GraphQL은 서버에 요청했을 때 예상했던 대로 돌려받고, 서버는 GraphQL을 통해 클라이언트가 요구하는 필드를 정확히 알고 있다.
    ➡️ 쿼리와 실행결과의 형태가 같음

  • 원하는 필드를 중첩하여 쿼리하는 것도 가능하다.
    GraphQL 쿼리는 관련 객체 및 필드를 순회할 수 있기 때문에 고전적인 REST API에서 그러했듯 다양한 endpoint를 만들어 각기 요청을 보내는 대신, 하나의 요청을 보냄으로써 관련데이터를 가져올 수 있다.


🔎 전달인자 (Arguments)

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


🔎 별명 (Aliases)

필드 이름은 중복으로 사용할 수 없으므로, 필드 이름을 중복으로 사용하여 쿼리해야 할 때에는 별명을 붙여 쿼리한다.
다른 이름으로 별명을 지정하면 한 번의 요청으로 두 개의 결과를 모두 얻어낼 수 있다.


🔎 오퍼레이션 네임 (Operation name)

오퍼레이션 타입에는 query, mutation, subscription, describes 등이 있다.
쿼리를 약식으로 작성하지 않는 한 오퍼레이션 타입은 반드시 필요하다.
또한 오퍼레이션 네임은 오퍼레이션 타입에 맞는 이름으로 작성하는 것이 가독성이 좋다.


🔎 변수 (Variables)

동적으로 인수를 받고 싶을 때 변수를 사용한다.
오퍼레이션 네임 옆에 변수를 $변수 이름: 타입 형태로 정의한다.

만약 $ep: Episode! 일 경우 변수 ep는 반드시 Episode여야 한다는 뜻!


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

GraphQL은 mutation이라는 키워드를 사용하여 서버 측 데이터를 수정한다.


🔎 스키마/타입(Schema/Type)

< GraphQL 스키마의 가장 기본적인 구성 요소 >
1️⃣ 서비스에서 가져올 수 있는 객체의 종류
2️⃣ 포함하는 필드를 나타내는 객체 유형

type Character {
  name: String!
  appearsIn: [Episode!]!
}
  • Character는 GraphQL 객체 타입이며, 즉 필드가 있는 타입임을 의미
    스키마에 있는 대부분의 타입은 객체 타입

  • nameappearIn은 Character 타입의 필드
    ➡️ nameappearIn은 GraphQL 쿼리의 Character 타입 어디서든 사용 가능한 필드!

  • String은 내장된 스칼라 타입 중 하나
    단일 스칼라 객체로 확인되는 유형 ➡️ 쿼리에서 하위 선택을 가질 수 없음
    스칼라 타입에는 ID, int도 있음

  • !가 붙는다면 이 필드는 nullable하지 않고 반드시 값이 들어온다는 의미
    이것을 붙여 쿼리한다면 반드시 값을 받을 수 있을 것이란 예상 가능

  • []는 배열을 의미, 배열에도 !가 붙을 수 있음
    ➡️ 위의 예시에서는 null 값을 허용하지 않으므로 항상 0개 이상의 요소를 포함한 배열을 기대할 수 있게 됨




💡 리졸버(Resolver)

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

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








📝 과제

🔎 .env파일에 토큰 환경변수 설정하기

// .env

REACT_APP_TOKEN=토큰

이때 REACT_APP_를 반드시 붙여줘야 react에서 인식 가능!


// App.js
const { REACT_APP_TOKEN, NODE_ENV } = process.env;

  let token;

  // NODE_ENV 가 개발 모드 / 테스트 모드일 때
  if (NODE_ENV === 'development' || NODE_ENV === 'test') {
    token = REACT_APP_TOKEN;
  }


...


  const { repository, viewer } = await graphql(
    `
    { /*요청하고자 하는 쿼리*/ }
    `,
    {
      headers: {
        authorization: `token ${token}`,
      },
    }
  );

  return { repository, viewer };
}








profile
안녕하세연🙋 프론트엔드 개발자입니다

0개의 댓글

관련 채용 정보