[개발자되기: GraphQL] Day-54

Kyoorim LEE·2022년 8월 2일
0

GraphQL

페이스북에서 만든 쿼리 언어
장점: REST API방식의 고정된 자원이 아닌, 클라이언트 요청에 따라 유연하게 자원을 가지고 올 수 있다!!

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

트리구조로 쿼리 결과를 받기 위해 그래프를 탐색하는 쿼리언어

GraphQL 특징

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

REST API 한계

Overfetch

필요없는 데이터까지 받아야함
유저의 이름만 필요한 상황에서 REST API를 사용한다면 이름 외에도 다른 여러 정보(주소, 생일)가 포함된 응답데이터를 받을 수 밖에 없음

Underfetch

필요한 것보다 덜 받는것
endpoint가 필요한 정보를 충분히 제공하지 못함
- 필요한 정보를 모두 가져오려면 REST API에서는 각 자원에 따라 엔드 포인트를 구분해서 보내줘야함

클라이언트 구조 변경 시 엔드포인트 변경 또는 데이터 수정 필요함

REST API에서는 자원의 크기와 형태를 서버에서 결정하므로 클라이언트에서 직접 데이터의 형태를 결정할 수 없음. 만약 클라이언트에서 필요한 데이터 내용이 변경될 경우 다른 endpoint를 통해 변경된 데이터를 가져오거나 수정해야함

REST API vs GraphQL

구분REST APIGraphQL
Resource에 대한 형태 정의와 데이터 요청방법연결완전히 분리
Resource 크기 형태 결정주체서버서버에서 Resource 정보만 미리정의하고 크기와 형태는 클라이언트에서 요청 시 결정함
Resource와 작업유형URI/MethodGraphQL Schema/Query,Mutation
Resource 접근 시요청 여러번 필요한번의 요청이면 됨
요청 처리 방식해당 엔드포인트에 정의된 핸들링 함수를 호출하여 작업처리요청 받은 필드에 대한 resolver를 호출하여 작업 처리

GraphQL의 장단점

장점

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

단점

  1. 캐싱이 REST API보다 복잡함
    • HTTP에선 각 메소드에 따라 캐싱이 구현되어 있으나 GraphQL에선 POST 메소드만을 이용해 요청을 보내기 때문에 각 메소드에 따른 캐싱을 지원받을 수 없음 => 보완책: Apollo 엔진의 캐싱과 영속 쿼리 등이 등장
  2. 고정된 요청과 응답만 필요할 경우에는 Query 로 인해 요청의 크기가 REST API 의 경우보다 더 커짐

GraphQL 구조

GraphQL 키워드

구분GraphQL비고
서버에서 데이터 조회(Read) 시Query를 이용해 원하는 데이터 요청REST API에선 GET요청 보냄
Create, Delete(저장된 데이터 수정 시)Mutation 이용

구독 (Subscription)

발행/구독(pub/sub) 모델을 따름
<--> 전통적인 client(요청)-Server(응답) 모델을 따르는 Query 또는 Mutation과 다름

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

쿼리(query, 데이터 조회)

필드(field)

  1. hero의 name을 쿼리
{
  hero {
    name
  }
}
# 쿼리 실행 결과 #
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

name은 String타입 반환
쿼리와 결과가 정확하게 같은 모양 (GraphQL에서 필수적)

  1. 히어로의 이름과 히어로의 친구 이름 같이 쿼리
{
  hero {
    name
    # 이런 식으로 GraphQL 내에서 주석도 작성할 수 있습니다.
    friends {
      name
    }
  }
}
# 실행 결과 #
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

필드 중첩하여 쿼라하는 것도 가능
다양한 endpoint를 만들어 각기 요청을 보내는 대신 클라이언트가 하나의 요청을 보냄으로써 관련 데이터 가져올 수 있음

전달인자(Arguments)

원하는 데이터만 받아오기

  1. id가 1000인 human의 name과 height를 쿼리
{
  human(id: "1000") {
    name
    height
  }
}
#쿼리 결과#
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

REST API를 이용한다면 ?id=1000 이거나 /1000(/:id)로 쿼리할 수 있음

별명 (Aliases)

필드 이름을 중복으로 사용해서 쿼리해야할 경우 별명을 붙여서 쿼리함

별명으로 쿼리

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}
# 쿼리 결과 #
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

오퍼레이션 네임(Operation name)

쿼리와 쿼리 네임을 모두 생략하는 축약형 구문을 사용했지만 실제 앱에서는 코드를 모호하지 않게 작성하는 것이 중요함

query keyword와 query name 작성

query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}
# 결과 #
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

변수(Variables)

동적으로 인수를 받고 싶을 때 변수 사용

변수 써서 작성된 쿼리

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

오퍼레이션 네임 옆에 변수를 $변수 이름: 타입 형태 로 정의. 위의 예시처럼 $ep: Episode 일 때, 뒤에 !가 붙는다면 ep는 반드시 Episode여야 한다는 뜻. !는 옵셔널.

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

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

GraphQL은 mutation 키워드를 이용하여 서버 측 데이터를 수정함
cf) REST API에서는 POST, PUT요청 사용하여 데이터 수정

mutation 이용하여 데이터 수정

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

스키마/타입(Schema/Type)

type Character {
  name: String!
  appearsIn: [Episode!]!
}
  1. Character는 객체타입
  2. nameappearInCharacter타입의 필드
  3. String은 내장된 스칼라 타입
  4. !가 붙는다면 이 필드에는 반드시 값이 들어온다는 의미
  5. [ ]는 배열을 의미하며 !도 붙을 수 있음. !이 붙는 경우 항상 0개 이상의 요소를 포함한 배열이라는 뜻

리졸버(Resolver)

스키마를 정의하면 그 스키마 필드에 사용되는 함수의 실제 행동을 Resolver를 통해 정의함

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

참고자료: https://graphql.org/learn/

profile
oneThing

0개의 댓글