Network

[API] GraphQL 구조

Mia_ 2023. 1. 30. 12:34

GraphQL Keywords

- 서버로부터 데이터를 조회(Read)하는 경우, REST API에서 GET 요청이 있었던 것 처럼 GraphQL에서는 Query를 이용해 원하는 데이터를 요청할 수 있음

- 또한 Create, Delete와 같이 저장된 데이터를 수정하는 경우에는 Muatation을 이용해 이를 수행할 수 있음

- 더불어 GraphQL에서는 구독(Subscription)이라는 개념을 제공하며 이를 이용해 실시간 업데이트를 구현 할 수 있음

→ Subscription는 전통적인 클라이언트(요청)-서버(응답) 모델을 따르는 Query 또는 Mutation과 달리 발행(pub)/구독(sub) 모델을 따름

→ 클라이어트가 어떤 이벤트를 구독하면, 클라이언트는 서버와 WebSocket을 기반으로 지속적인 연결을 형성하고 유지하게 됨 

→ 그 후 특정 이벤트가 발생하면, 서버는 대응하는 데이터를 클라이언트에 푸시해줌

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

 

 

쿼리(query, 데이터 조회)

 

 1. 필드(field)

- query를 실행 했을 때 얻은 결과 예시

## bias의 name을 쿼리

{
  bias {
    name
  }
}
## query를 실행했을 때의 결과
{
  "data" : {
    "bias" : {
      "name" : "wonpil"
    }
  }
}

- 필드의 name은 String type을 반환함 

- 쿼리와 쿼리의 결과가 같은 모양을 하고 있으며 이는 GraphQL에서 필수적임

- GraphQL은 서버에 요청했을 때 예상했던 대로 돌려받고, 서버는 GraphQL 을 통해 클라이언트가 요구하는 필드를 정확하게 알기 때문

 

- query를 실행 했을 때 얻은 결과 예시 2

## bias의 이름과 bias의 친구 이름을 같이 쿼리
{
  bias {
    name
    # 이런 식으로 GraphQL 내에서 주석도 작성할 수 있습니다.
    friends {
      name
    }
  }
}
## 최애의 이름과 최애의 친구의 이름이 조회 됨
{
  "data": {
    "bias": {
      "name": "wonpil",
      "friends": [
        {
          "name": "sungjin"
        },
        {
          "name": "young hyun"
        },
        {
          "name": "dowoon"
        }
      ]
    }
  }
}

- 이런 식으로 필드를 중첩하여 쿼리하는 것도 가능

- 위의 예에서 friends 필드는 배열을 반환 

- GraphQL 쿼리는 관련 객체 및 필드를 순회할 수 있기 때문에 고전적인 REST API에서 그랬듯이 다양한 endpoint를 만들어 각기 요청을 보내는 대신 → 클라이언트가 하나의 요청을 보냄으로써 관련 데이터를 가져올 수 있음

 

2. 전달인자(Arguments)

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

## id가 1004인 human의 name과 height를 query
{
  human(id:"1004") {
    name
    height
  }
}
## query 결과
{
  "data" : {
    "human" : {
      "name" : "younghyun",
      height : 180
    }
  }
}

- 이런식으로 id가 1004인 human의 name과 키를 쿼리해 올 수 있음

- REST API와 같은 시스템에서는 단일 인수 집합(요청의 쿼리 매개변수 및 URL 세그먼트)만 전달 할 수 있음

- 예를 들어 REST API를 이용한다면 ?id=1004 이거나 /1004(/:id) 일 때와 같은 같은 목적으로 쿼리할 수 있음

 

3. 별명(Aliases)

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

## 이런 식으로 중복해 쿼리 할 수 없음
{
  rings(episode: return of the king) {
    name
  }
  rings(episode: The Two Towers) {
    name
  }
}
## 앞에 알아 볼 수 있는 별명을 붙여주면 쿼리할 수 있음
{
  kingRings : rings(episode: return of the king) {
    name
  }
  towersRings: rings(episode: The Two Towers) {
    name
  }
}
## query  결과를 받아올 수 있음
{
  "data" : {
    "kingRings": {
      "name" : "destoryed"
    },
    "towersRings": {
      "name" : "prodo"
    }  	
  }
}

 

4. 오퍼레이션 네임(Operation name)

- 여태껏 쿼리와 쿼리 네임을 모두 생략한 축약한 구문만 예시로 보았으나 실제 앱에서는 코드를 모호하지 않게 작성하지 않는 것이 중요

## 이런식으로 query keyword와 query name을 작성함
query BiasNameAndFriends {
  bias {
    name
    # 이런 식으로 GraphQL 내에서 주석도 작성할 수 있습니다.
    friends {
      name
    }
  }
}

- 앞의 query는 오퍼레이션 타입임

- 오퍼레이션 타입에는 query 뿐만 아니라 muation, subscription, describes 등이 있음

- 쿼리를 약식으로 작성하지 않는 한 이런 오퍼레이션 타입은 반드시 필요함 

- 오퍼레이션 네임을 작성 할 때는 오퍼레이션 타입에 맞는 이름으로 작성하는 것이 가독성이 좋음

 

5. 변수(Variables)

- 지금까지의 예제는 고정된 인수를 받아 쿼리했지만 실제 앱에서는 고정된 인수를 받는 것보다 동적으로 인수를 받아 쿼리하는 경우가 대다수임

- 변수는 그런 인수들을 동적으로 받고 싶을 때 사용 

## 변수를 써서 작성된 쿼리

query MainRoleAndFirends($episode: Episode){
  mainrole(episode: $episode) {
    name 
    friends {
      name
    }
  }
}

- 오퍼레이션 네임 옆에 변수를 $변수 이름 : 타입 형태로 정의 

- 위의 예시 처럼 $episode : Episode 일 때, 뒤에 ! 가 붙는다면 episode는 반드시 Episode 여야 한다는 뜻 

- ! 는 옵셔널한 사항

 

 

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

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

- REST API에서 GET 요청을 사용을 사용하여 데이터를 수정하지 않고, POST 혹은 PUT 요청을 사용하는 것과 유사

- 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 객체 타입이며, 즉 필드가 있는 타입임을 의미. 스키마에 있는 대부분의 타입은 객체 타입임

→ name과 appearsIn은 Character 타입의 필드임. 즉 name과 appersIn은 GraphQL 쿼리의 Character 타입 어디에서든 사용할 수 있는 필드임

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

→ ! 가 붙는다면 이 필드는 nullable 하진 않고 반드시 값이 들어온다는 의미. 이것을 뒤에 붙여 쿼리한다면 반드시 값을 받을 수 가 있을 것이란 예상을 할 수 있음

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

 

 

리졸버(Resolver)

- 요청에 대한 응답을 결정해 주는 함수로써 GraphQL의 여러 가지 타입 중 Query, Mutation, Subscription과 같은 타입의 실제하는 방식 즉 로직을 작성함

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

- 또한 이러한 함수들이 모여 있기 때문에 보통 Resolvers라고 부름

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 요청)을 Resovler가 담당하게 됨 

 

 

link : GraphQL의 Learn