Information

[GraphQL] GraphQL이란?, REST API와 차이점

y0ngha 2021. 12. 16. 23:21

Graph QL(GQL)

API 서버에서 엄격하게 정의된 End-point들에 요청하는 대신 한번의 요청으로 정확히 가져오고 싶은 데이터를 가져올 수 있게 도와줌

User에 대한 몇가지의 간단한 데이터가 필요할 때 필요한 만큼 정보를 가져올 수 있음.

Back-end에서의 많은 Logic을 Front-end로 분산하여 처리가 가능

GQL, REST API의 차이점

  REST API Graph QL
요청 Resource에 대한 형태 정의, Data 요청 방법이 정의되어 있다. Resouce에 대한 형태, 데이터 요청이 분리되어 있다.
크기와 형태 서버에서 결정한다. Resource에 대한 정보만 정의하고, 필요한 크기와 형태는 Front-end에서 요청시 결정한다.
Resource & Task Resouce = URI, Method = 작업 유형을 나타낸다. Schema가 Resouce, Query, Mutation이 작업 유형이다.
GQL에서는 보통 POST요청만 진행하며, 한개의 End-Point만 존재한다.
여러 데이터를 가져올 때 여러번 호출을 진행해야한다.(Over-fetching)
(User, Book, Author를 가져오기 위해선 3번 요청)
한번의 요청으로 여러 Resource를 가져올 수 있다.
(User, Book, Author를 1번의 요청으로 가져옴)
요청 처리 방법 요청은 해당 End-point에 정의된 핸들링 함수를 통해 작업을 처리한다. 요청 받은 각 Field에 대해 Resolver를 호출하여 작업을 처리한다.

각각(GQL, Rest API)의 요청 방법 정리

Rest API

  • 각각의 Resource는 URL 끝점으로 분리된다.
  • 특정 Method를 통해 Data를 검색(R)하거나, 조작(CUD)한다.
요청 : [GET] /book/1
설명 : 데이터베이스에서 1번 책에 대한 정보를 가져온다
// 응답 //
{
    "title": "토끼와 거북이",
    "author": {
        "firstName": "",
        "lastName": "Aesop"
    }
}

Graph QL

  • Type을 정의한다.
  • 요청을 위한 Query 타입 작성을 한다.(Root Query라 칭함)
  • Query(R), Mutation(CUD)으로 분리된다.

(Back-end에서 Front-end의 요청을 받아들이기 위해 작성하는 것, 확장자는 *.graphqls)

// Type 정의 ( Book, Author ) ( 서버역할 ) //
type Book {
    id: ID
    title: String
    author: Author
}

type Author {
    id: ID
    firstName: String
    lastName: String
    books: [Book]
}
// 요청을 위한 Root Query 작성 ( 서버역할 ) //
type Query {
    book(id: ID!): Book
    author(id: ID!): Author
}
요청 : [POST] /graphqls
Body :
{
    book(id: "1"),
    author {
        lastName
    } 

설명 : 1번 책에 대한 정보와, 저작자의 마지막 이름을 갖고온다.
// 응답 //
{
    "title": "토끼와 거북이",
    "author": {
        "lastName": "Aesop"
    }
}

GQL의 경우 위처럼 Schema만 정의되어 있다면 FE에서 원하는 데이터만 가져갈 수 있다.

Graph QL Introspection

Rest API를 개발해본 적 있거나, BE 개발자와 협업을 한 FE 개발자라면 Rest API를 관리하기 위해 swagger 를 사용했을 것이다.

이와 같이 Graph QL에도 Introspection이라는 것을 이용해 관리할 수 있다.

서버 자체에서 현재 서버에 정의된 Schema를 실시간으로 공유한다.

→ 이는 기존 API 명세서의 단점을 보완한 것이다. (BE 개발자가 명세서 전달 타이밍을 놓치거나, 까먹거나, 명세서에는 작성했으나 반영을 안하는 등..)

 

introspection용 Query가 존재한다.

  • 일반 GQL 작성 하듯 작성을 진행한다.
  • 하지만, 실제로는 작성할 필요가 없다. 대부분의 서버용 GQL 라이브러리가 Query용 IDE를 제공한다.

Object Type & Field

type Book {
    id: ID!
    title: String!
    page: Int!
    viewer: [User!]
}

Object Type : Book

Field : id, title, page, viewer

Type : ID, String, Int(Scalar Type), 이 외에도 존재, User(Custom Type)

Non-nullable : "!"(느낌표)

Array : [ ]

Resolver

Data를 가져오는 구체적인 과정을 의미함.

  • BE 개발자가 직접 개발하여 구현해야함
    (불편하긴 하지만, 한번 구현을 하면 Data source에 관계 없이 가져올 수 있음.)
  • Database, 일반 File 과 더불어 http, soap과 같은 Network 통신을 이용해 데이터를 가져올 수 있음
    이는 흔히 말하는 Legacy Code를 Refactoring 하는데 유리함
  • 각각의 필드마다 함수가 하나씩 존재한다고 생각하면 됨.
    그 함수는 다음 Type을 반환하는데, 이를 Resolver라고 함.
  • 만약 필드가 Scalar Type인 경우 함수를 종료하고, Custom Type인 경우에 해당 Type의 Resolver로 이동함.(DFS)

그래서 도입하면 뭐가 좋은데?

  • BE 개발자가 맡아 진행하던 업무를 FE 개발자에게 분산시켜 업무를 효율적으로 진행할 수 있게함.
    (Rest API로 진행할 경우 FE 개발자가 원하는 형식의 데이터가 달라질 때 마다 BE 개발자에게 요청을하고, 명세서를 다시 작성 후 건내주는 식으로 작업을 진행하는데 Graph QL 도입시 그 작업을 FE 개발자가 진행하기 때문에 Delay 없는 효율적인 업무가 가능하다고 생각됨.)
  • BE 의존도가 줄어듬.
    BE 개발자가 전달해준 Request, Response, End-point에 FE 개발자는 100% 의존하여 사용중이었는데 Graph QL 도입으로 FE 개발자는 Schema만 의존하면 됨.
  • Over-fetching이 줄어듬
    FE에서 원하는 형식의 데이터를 갖고오기 위해 여러번의 요청 후 데이터를 짜집기 하던 방식을 1번의 요청으로 처리가 가능함.

근데 왜 안할까?

  • Graph QL을 도입하기 위해서는 BE 개발자가 Graph QL에 대해 완전히 이해하고 있어야 하는데, 이를 학습하는데에는 시간이 오래 걸림.(learning curve)
  • 기존 Rest API를 이용한 개발자(FE/BE)라면 Graph QL에 대해 익숙해지기 까지 오래걸림

→ Rest API는 오랫동안 개발자들에게 사랑 받으며 쓰여 왔는데 해당 부분을 단시간에 바꾸기란 쉽지 않음.

GraphQL, Rest API 그림으로 요약

출처) https://hwasurr.io/api/rest-graphql-differences/

Rest API는 /user, /book, /author 각기 다른 End-point를 이용해 호출하고 결과값을 받아와야 하지만,

Graph QL은 한번의 요청으로 Body에 있는 Query를 해석해 각 필드의 resolver가 호출되며 값을 반환한다.

조금 더 자세히 들어가보자

아래의 이미지를 보고 해석해보도록 하자.

출처) https://gist.github.com/loganpowell/56af8f0e2b68af21f88833075092f864

  1. Client(Front-end)에서 호출하는 쿼리다.
  2. 서버에 정의 되어 있는 Root Query resolver에 도착해 user라는 Schema를 갖고오기 위해 fetchUserById를 실행한다.
    이 때 fetchUserById의 반환 객체는 { "user" : { "id": "abc", "name" : "Sarah" } }다.
  3. user Schema 안에 있는 id와 name 필드를 해석하기 위해 3번과 4번으로 각자 이동된다.
    (이 때 resolver는 병렬처리 되는 것으로 알고 있다.)
  4. 필드에 대한 resolver 실행이 끝나면 2번 작업이 종료된다.
  5. Client(Front-end)로 응답을 준다.

resolver의 인자는 총 4개로 구성되어 있다.

  1. parent : 부모 resolver가 리턴한 값
  2. args : Query에서 입력으로 넣은 값
  3. context : 모든 resolver에게 전달되는 값. 주로 Middle-ware에서 입력된다.
    (예를 들면 로그인 정보, 권한 등.)
  4. info : Schema 정보, 현재 Query의 특정 필드 정보를 담고 있다.
    (이 인자는 잘 사용되지 않는다고 한다.)

일반 쿼리, 오퍼레이션 네임 쿼리

GraphQL을 사용할 때 Query를 작성하는 방법은 2가지로 나뉘어져 있다.

일반쿼리와, 오퍼레이션 네임 쿼리로 구분되어 있는데 두가지의 차이점은 Static / Dynamic이다.

보통 Client에서 서버로 요청을 할 때 정적인 데이터로 요청을 하는 경우는 드물 것이다.

// 정적 데이터 요청
{
    book(id: "1") {
        title
    }
}

위와 같이 요청을 할 때부터 "나는 ID 1번에 대한 것을 갖고올거야!" 라고 정하는 것을 의미한다.

 

오퍼레이션 네임 쿼리의 경우 Client에서 동적인 값을 넣어 Query로 만들어 요청할 수 있다.

// 동적인 데이터를 넣어 요청
query GetBookInfo($id: ID) {
    book(id: $id) {
        title
    }
}

GQL Library

대표적인 라이브러리로는 Relay, Apollo GraphQL이 있다.

구현시 주의되어야 할 점

  • 하나의 Root Mutation, 하나의 Root Query로 구현되어야 한다.
  • 비즈니스 로직은 실제 리졸버(resolver) 함수에 넣지 말아야 한다.
  • 로직은 비즈니스 로직 레이어에 작성할 것.(다른 파일, 다른 함수)
    (이는 Rest API를 작성하는 방법과 동일하게 생각하면 된다.)

마치며

요즘 핫한 패러다임인 Graph QL에 대한 기초 지식만 공부를 했는데도, Rest API와 너무 많은 점이 달라서 적응하기가 힘들 것으로 예상된다.

평소 FE 개발을 진행하면서 답답했던 부분들을 해소시켜줄 수 있을 것이라고 본다.

(BE 개발자님, ~~ API에서 이 필드 추가해주시고, 어떤 객체를 리턴하는 API 하나만 만들어서 주세요!.. 하고 Mock 데이터로만 테스트 하다가 실제 데이터를 받아보면 서로 달라서 오류가 나 당황하는 둥. 여러 부분)

하지만, BE 개발자 관점에서 바라본다면 이는 새로운 방식으로 당장 업무에 도입을 하기에는 힘들 것이라고 예상된다.

(임시로 프로젝트를 하나 생성해 React + Spring boot로 Graph QL을 통해 데이터를 주고 받는 프로젝트를 빨리 작성해 코드 리뷰를 하며 공유 해보도록 하겠다.)

 

분명 훌륭한 기술임은 틀림 없으나, 러닝 커브가 너무 심해 확실한 예제를 보며 코드를 분석하고 공부를 한 후 진행해야 될 것 같다.

(GraphQL을 공부해 개인 프로젝트에 반영 시킬 예정이다.)

 

참고)

https://gist.github.com/loganpowell/56af8f0e2b68af21f88833075092f864

https://hwasurr.io/api/rest-graphql-differences/

https://tech.kakao.com/2019/08/01/graphql-basic/

https://velog.io/@jangwonyoon/1.-GraphQL-%EA%B0%9C%EB%85%90