[GraphQL] GraphQL 101


GraphQL

오랜만에 Kotlin, Android 외의 기술 포스트를 하는 것 같다. 사실 GraphQL이 핫한지는 꽤 오래되었고, 그 당시 끄적거려보기도 했으나 마땅히 쓸만한 곳이 없어 오랫동안 내 머리속 한켠에 밀려있었다.

다행이 토이 프로젝트에 사용할 일이 있었고(굳이 말하자면, 사용할 것을 결정하였고) 이에 GraphQL에 대한 짧은 글을 포스팅하게 되었다.

What Is It?

GraphQL 은 새로운 API 표준이다. 사실 많이 헷갈리는 것이 database에 대한 질의어 정도로 생각하는 경우가 많은데 이는 틀렸다. Query Language이긴 하나, database가 아닌 API 를 위한 Query이다.

Facebook에 의해 개발 및 오픈소스화 되었고, 상당히 큰 오픈소스 단체에 의해 매니징 되고 있다. 기회가 된다면, 컨트리뷰터가 되는 것도 참 좋겠다.

GraphQL : The New API Standard. A Query Language for APIs(not databases).

GraphQL에 대해서 살펴보기 전에 API의 진화에 대해서 짧게 살펴보자. (사실 RESTful API가 생각보다 단순한 컨셉인 것처럼, GraphQL도 별건 없다. 다만, RESTful API에 비해 명세가 구체화되어있고, 그 사용을 강제하기 때문에 상대적으로 명료한 이점이 있다.)

(또 첨언을 하자면, REST API는 RESTful과 REST-Like로 나뉘어서 사용된다. 즉, 단순 명세를 어떻게 구현하느냐에 따라 여러 구현체가 존재할 수 있다. (제각각 동작이 상이 할 수 있다.))

(본론으로 돌아와서) API 의 진화는 다음과 같다. (Server-Client Model, Socket Programming, API에 대한 이해는 다들 알고 있을 것이라 가정한다.)

RPC -> SOAP -> REST -> GraphQL

RPC는 Remote procedure Call의 acronym으로서, Server에 정의되어있는 IDL(Interface Definition Langugae)을 활용하여 통신을 한다. 즉, Server-Client 간 소켓이 어쩌구 하는 등의 일을 RPC가 대신하게 되고, 개발자는 단순히 IDL을 구현하기만 하면 된다는 컨셉이다. (소켓 및 복잡한 low level에 대한 캡슐/추상화를 통해 간단한 인터페이스 호출로 네트워킹)

SOAP은 Simple Object Access Protocol을 뜻한다. 이는 통신을 위해 Web 을 이용해보자는 컨셉으로부터 출발한다. 즉, HTTPXML을 이용하여 데이터를 주고 받는 프로토콜이다.

REST는 URI를 하나의 자원으로 표현한 방식을 뜻한다. REST는 REpresentational State Transfer를 뜻한다. 뭐, 너무나 익숙한 컨셉이니 본문에서는 더 이상의 설명을 생략한다.

대망의 GraphQL은 위에서 짧게 언급한 것처럼, 새로운 API 패러다임이다. REST가 여러개의 end point를 가지는 것을 “단점”으로 보고, single end point를 가진 API 명세를 고안하였고, 우리는 이에 대해 하나하나씩 살펴볼 것이다.

REST의 문제점과 GraphQL의 장점

  1. 모바일 사용 증가로 인해, 효율적인 데이터 로딩이 필요
    • REST : 리소스의 개수만큼 network call이 필요하다. 하지만 정작 리소스는 client에서 “조합”되어 한개의 데이터처럼 사용된다.
    • GraphQL : 데이터 양을 감소시킨다. 즉, REST 보다 훨씬 적은 network call로 동일한 동작을 구현 할 수 있다.
  2. 프론트엔드 프레임워크/플랫폼이 매우 다양해짐
    • REST : 프레임워크별로 REST API 를 개발 및 유지보수 하는 것이 힘듦
    • GraphQL : GraphQL은 단순하다. 유지보수가 수월하다.
  3. 빠른 개발이 필요한 시대적 상황
    • REST : CD(Continuous deployment)은 이제 대세(스텐다드)가 되었음. 빠른 iteration과 잦은 배포는 필수적임. REST API의 변경은 Server 뿐만 아니라, Client 코드도 변경되어야 함.
    • GraphQL : 빠른 개발 ! 가능하다. 한번 시도해보시라 !

본격 비교 | REST vs GraphQL

개발자는 코드로 얘기해야한다. 코드로(실제 코드는 아니지만) 살펴보자 !

요구사항)
SNS 서비스의 메인화면에서 다음을 노출하고 싶다.
- User Profile
- User가 등록한 Posts
- User의 Follower List

REST

위의 “요구사항”이 3가지(Profile, Post, Follower)이므로, 3번의 request가 필요하다. (3가지 resource이기 때문)

REST

우리가 흔히 생각 할 수 있는 모습이다. 하지만, GraphQL로 위의 요구사항을 구현하면 어떻게 될까?

GraphQL

GraphQL은 Type SystemSchema 가 있다 !

graphql

참 쉽지 아니한가!

GraphQL의 핵심

SDL (The Schema Definition Language)

GraphQL은 Type System을 따른다. 위의 예시에서는 User와, Post, Follower를 사용하였는데 아래와 같이 정의 할 수 있다.

String, Int 등의 Primitive 타입으로 필드 타입을 정의하면서 ! 를 이용하여 해당 필드는 필수임을 나타 낼 수 있다.

// User Type1
type User {
    name: String!
    nickname: String
}

// User Type2 which has both of posts and followers fields
type User {
    name: String!
    nickname: String
    posts: [Post!]!
    followers: [User!]!
}

type Post {
    title: String!
    comments: [Comment!]!
}

아래의 예시를 한개 씩 살펴보며, 의미를 이해해보자.

// Query to get all users, and posts which is written by each of users with "query"
{
  allUsers {
    name
    posts {
        title
    }
  }
}

// Create a new user with "mutation" - creating, updatting, deleting
mutation {
  createUser(name: "Newbie", nickname: "abc") {
      name
      bdnicknameay
  }
}

// Subscribe a specific events with "subscribe" - like websocket push
subscription {
  newUser {
    name
    nickname
  }
}

type Query {
  allUsers(last: Int): [User!]!
}

type Mutation {
  createUser(name: String!, nickname: String!): User!
}

type Subscription {
  newUser: User!
}

Codelab

자, 이제 GraphQL을 다 배웠다. (응?) 써먹어보자 !

Java 등 환경에서 서버/클라를 구축할수도 있지만, 빠른 개발(?)을 위해 nodejs/express 로 서버를 띄우고 클라이언트를 구성해보자.

1. nodejs server

$ npm init
$ npm install graphql express express-graphql -save

위와 같이 프로젝트를 설정하고나서, index.js 파일에 다음과 같은 서버 코드 및 GraphQL 코드를 작성한다.

$ vi index.js

const express = require('express');
const express_graphql = require('express-graphql');
const { buildSchema } = require('graphql');
// GraphQL schema
const schema = buildSchema(`
    type Query {
        user(id: Int): User
        allUsers(last: Int): [User!]
    }

    type Mutation {
        createUser(name: String!, bday: Int!): User!
    }

    type Subscription {
        newUser: User!
    }

    type User {
        id: Int!
        name: String!
        nickname: String!
    }
`);

// Root resolver
const users = [
    {
        id: 1,
        name: "user1",
        nickname: "a"
    },{
        id: 2,
        name: "user2",
        nickname: "b"
    },{
        id: 3,
        name: "user3",
        nickname: "c"
    }
];

const root = {
    user: (arg) => {
        return users.filter(user => {
            return user.id === arg.id;
        })[0];
    },
    allUsers: (arg) => {
        if (arg.last) {
            try {
                return users.slice(-1 * arg.last)
            } catch (e) {
                return users;
            }
        }

        return users;
    },
};

// Create an express server and a GraphQL endpoint
const app = express();
app.use('/graphql', express_graphql({
    schema: schema,
    rootValue: root,
    graphiql: true
}));
app.listen(4000, () => console.log('Express GraphQL Server Now Running On localhost:4000/graphql'));

그리고 나서, 다음과 같이 서버를 실행한다.

$ node index.js

위와 같이 실행한 후, postman 이나 curl 등으로 request를 날려보자 !

$ curl -X POST \
       -H "Content-Type: application/json" \
       -d '{ "query": "{allUsers(last: 5) {name} user(id: 2) {name}}" }'

위와 같이 실행하면, 아래와 같은 응답을 받을 수 있다.

{
  "data": {
    "allUsers": [
      {
        "name": "user1"
      },
      {
        "name": "user2"
      },
      {
        "name": "user3"
      }
    ],
    "user": {
      "name": "user2"
    }
  }
}

Reference

  • https://www.howtographql.com/basics/1-graphql-is-the-better-rest/