Section 4 GraphQL

keepgoing·2023년 3월 28일

코드스테이츠

목록 보기
21/31
post-thumbnail

🔑 용어 체크
스칼라 타입 : 단 하나의 값만을 저장할 수 있는 데이터 타입
컴포지트 데이터 타입 : 두개 이상의 값을 저장할 수 있는 데이터 타입

✅ GraphQL

오픈 소스로 제공된 쿼리 언어.

  • API를 위한 쿼리 언어

🤔 왜 Graph를 사용하나요?

  • 그래프는 여러 개의 점들이 서로 복잡하게 되어 있는 관계를 표현한 자료 구조


🤔 그래프 자료 구조와 트리 구조의 차이 이미지

  • GraphQL에서는 모든 데이터가 그래프 형태로 연결되어 있다고 전제.
  • GraphQL은 클라이언트 요청에 따라 유연하게 트리 구조의 JSON 데이터를 응답으로 전송
  • 장점 : GraphQL은 REST API 방식의 고정된 자원이 아닌 클라이언트 요청에 따라 유연하게 자원을 가져올 수 있다는 점

GraphQL로 그래프 순회

그래프에서 트리 추출 방법

  • 위 그래프 이미지에서 실행할 수 있는 쿼리
query {
	책(ISBN이 "9780674430006") {
		책 이름
		저자 {
			이름
		}
	}
}
  • 한 권의 책만 검색하기 위해, ISBN이 "9780674430006인 조건을 걸어주겠습니다. 이 방식으로 서버에 요청을 보내고, 서버가 해당 요청을 해결한다면, 돌아온 쿼리는 이럴 것 이다.
{
	책 : {
		책 이름 : "GraphQL은 어렵지 않다",
		저자 : [
			{ 이름 : "김코딩"},
			{ 이름 : "박해커"},
		]
	}
}

  • ISBN 번호를 사용하여 선택한 “책" 노드에서 시작합니다.
  • 그 다음 GraphQL은 중첩된 각 필드로 표시된 간선을 따라 그래프를 탐색하기 시작합니다.
  • 즉 쿼리 내 중첩된 “책 이름” 필드를 통해 책의 제목이 있는 노드로 이동합니다.
  • 그러면서 “저자”로 레이블이 지정된 “책”의 간선을 따라가 “저자” 노드를 가져오고, 각 저자의 “이름"을 얻어오는 것입니다. 이것을 트리 구조로 표현하면 이렇게 보입니다.

  • 이렇게 GraphQL의 중첩된 필드를 그래프의 계층 구조로 표현하면 이렇게 트리 구조로도 표현할 수 있게 됩니다.
  • 즉, GraphQL은 트리 구조로 쿼리 결과를 받기 위해 그래프를 탐색하는 쿼리 언어라고 볼 수 있습니다.

🤔 GraphQL의 특징

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

✅ GraphQL의 장단점

👍 장점

GraphQL로 Blog 앱을 구현할 때

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

GraphQL의 단점

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

GraphQL 구조

GraphQL Keywords

  • ❤️ Rest API에서 GET 요청이 있었다면,
  • ✅ GraphQL에서는 Query를 이용해 원하는 데이터 요청
  • ❤️ Rest API에서 Create, Delete와 같이 저장된 데이터를 수정한다면
  • ✅ GraphQL에서는 Mutation을 이용해 이를 수행
  • ✅ GraphQL에서는 구독(Subscription)이라는 개념을 제공
    -> 클라이언트가 어떤 이벤트를 구독하면, 클라이언트는 서버와 WebSocket을 기반으로 지속적인 연결을 형성하고 유지
    -> 그 후 특정 이벤트가 발생하면, 서버는 대응하는 데이터를 클라이언트에 푸시
  • Query: 저장된 데이터 가져오기 (REST의 GET과 비슷합니다.)
  • Mutation: 저장된 데이터 수정하기
    • Create: 새로운 데이터 생성
    • Update: 기존의 데이터 수정
    • Delete: 기존의 데이터 삭제
  • Subscription: 특정 이벤트가 발생 시 서버가 대응하는 데이터를 실시간으로 클라이언트에게 전송

쿼리(query, 데이터 조회)

필드(field)

간단한 쿼리와 실행 결과

//실행
{
  hero {
    name
  }
}

//결과
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}
  • 필드의 name은 String 타입을 반환
  • 쿼리와 결과가 정확하게 같은 모양(GraphQL에 있어서 필수적)
  • 서버에 요청 시 예상한대로 돌려받고, 서버는 GraphQL을 통해 클라이언트가 요구하는 필드를 정확히 알기 때문.

히어로의 이름과 히어로의 친구 이름을 같이 쿼리

//요청
{
  hero {
    name
    # 이런 식으로 GraphQL 내에서 주석도 작성할 수 있습니다.
    friends {
      name
    }
  }
}
//결과
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}
  • 필드를 중첩하여 쿼리하는 것도 가능
  • freinds 필드는 배열을 반환
  • REST API에서 그러했듯 다양한 endpoint를 만들어 각기 요청을 보내는 대신 클라이언트가 하나의 요청을 보냄으로써 관련 데이터를 가져올 수 있습니다.

전달인자(Arguments)

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

//요청
// id가 1000인 human의 name과 height를 쿼리
{
  human(id: "1000") {
    name
    height
  }
}

// 결과
//  id가 1000인 human의 이름과 키를 쿼리
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

별명(Aliases)

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

// ❌ 필드 이름을 중복해서 사용 불가.
{
  hero(episode: EMPIRE) {
    name
  }
  hero(episode: JEDI) {
    name
  }
}

// 알아볼수 있는 별명 작성 시 쿼리 가능
// 요청
{
  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"
        }
      ]
    }
  }
}

query 는 오퍼레이션 타입

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

변수(Variables)

  • 고정된 인수를 앞서 설명했지만 실제 앱에서는 동적 인수를 받아 쿼리하는 경우가 대다수
  • 변수는 인수들을 동적으로 받고 싶을 때 사용
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
  • 오퍼레이션 네임 옆에 변수를 $변수 이름: 타입 형태 로 정의
  • $episode: Episode 일때, $episode: Episode!가 되면 반드시 episode는 Episode여야 한다는 뜻. !는 옵셔널한 사항.

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

  • GraphQL은 대개 데이터를 가져오는 데에 중점을 두고 있지만 서버측 데이터를 수정
  • ❤️ REST API에서 GET 요청을 사용하여 데이터를 수정하지 않고, POST 혹은 PUT 요청을 사용하는 것처럼 GraphQL도 유사하다.
  • GraphQL은 mutation 키워드를 사용하여 서버측 데이터 수정
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}

스키마/타입(Schema/Type)

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

type Character {
  name: String!
  appearsIn: [Episode!]!
}
  • Character은 GraphQL 객체 타입이며, 필드가 있는 타입임을 의미. 스키마에 있는 대부분의 타입은 객체 타입
  • nameappearInCharacter 타입의 필드
  • String은 내장된 스칼라 타입 중 하나입니다.
  • !가 붙는다면 필드는 nullable 하지 않고 반드시 값이 들어온다는 의미.
  • [] 배열을 의미. 배열에도 !가 붙을 수 있다.(항상 0개 이상의 요소를 포함한 배열)

리졸버(Resolver)

  • 요청에 대한 응답을 결정해주는 함수로써 GraphQL의 여러 가지 타입 중 Query, Mutation, Subscription과 같은 타입의 실제 일하는 방식, "로직 작성"
  • 스키마 필드에 사용 되는 함수의 실제 행동을 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 () => {
      ...
		}
  }
};
profile
매일매일

0개의 댓글