GraphQL

안드로이드 native front-end 개발할때는 뭔가했었던 이야기들을 직접 때려 넣으려니 지금도 뭔가싶지만 그래도 단순하게 개발블로그나, 서적, 문서를 봐도 뭔지 모르겠던 것들이 조금씩 가닥은 잡히는 느낌이다. 프로젝트도 얼마 남지 않았고, 취업전선에 뛰어들날도 얼마남지 않았다. 정 안될때의 시나리오도 있지만 그건 그때 걱정하고 어차피 걱정의 걱정을 뭐더러 걱정하냐는 티벳의 속담처럼 가자.


1. GraphQL

A query language for your API
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

당신의 API를 위한 쿼리언어
그래프큐엘은 쿼리언어 API, 런타임 당신의 존재하는 데이터 쿼리 이행. 제공해 완벽하고 이해가능한 설명, 데이터 니 API, 고객에게 정확히 뭘원하는가와 그이상을, 더 쉽게해줘 시간이 가도 유지보수하기, 그리고 강력한 개발툴을 쓸수있어

요약: 좋다 써라

2. 개요

이것역시 메타에서 만든 쿼리 언어이다. 2016년에 등장해서 react와 함께 시대를 풍미하고 있는 중이다. REST API를 대체할지도(안할수도있지만) 모르는 GraphQL, 메타는 언젠가 오라클처럼 되지 않을까

3. 개념

오픈소스로 제공된 쿼리 언어, Graph+Query Language, Server API를 통해 정보를 주고받기위해 쓰는 쿼리언어. -> API를 위한 쿼리언어

아이디어 그래프로 시작, 그래프 자료구조가 인간 뉴런구조와 비슷하다. 그래서 현실과 현상을 모델링할 수 있고, 마인드스토밍과 유사한 데이터구조를 가진다.

여러개의 점들이 서로 복잡하게 연결되어있는 관계를 표현하는게 그래프자료구조인데, 하나의 점을 node, vertex라고하며, 선을 edge, 직접관계가 있으면 두 점을 이어주는 선이 있고, 간접관계라면 몇개의 점과 선에 걸쳐 이어진다. 노드간의 간선을 통해 특정순서에따라 그래프를 재귀적으로 탐색할 수 있다.

GraphQL은 모든 데이터가 그래프 형태로 연결되어있다고 가정한다. 1:1, 여러 level로 이루어진 관계도 역시 그래프이다. 그래프의 정렬기준이 어디냐에따라 트리구조를 이룰수도 있다.

GraphQL은 ct의 요청에 따라 유연하게 트리구조의 json데이터를 응답으로 전송할 수 있다. rest api방식의 고정된 자원이 아닌, ct의 요청에 따라 유연하게 자원을 가져올 수 있어 좋다.

3.1. 그래프 순회

그래프로 표현하게되면 우리가 가지고 있는 데이터의 조각들이나 나타내고자하는 entity간의 관계를 나타낼 수 이있다.

Entity: 사물의 구조나 상태, 동작 등을 모델로 표현하는 경우, 그 모델의 구성요소를 말한다. 정보의 측면에서는 속성 자체만으로 중요한 의미를 표현하지 못해 단독으로 존재하지 못한다. 개별적으로는 유의미하지 못할지는 몰라도 여러개의 entity가 모이면 유의미한 정보를 제공할 수 있다.

트리는 방향성은 존재하지만 사이클은 존재하지않는 비순환 그래프이다. 루트와 모서리를 통해 노드를 따라 순회할 수 는 있지만, 동일한 노드로 돌아올 수 없는 속성을 갖는 특별한 그래프이다.(그래서 관계형 db가 있는건가요?)

3.2. 그래프에서 트리 추출

query {
        goods(code: "10010110"){
                goodsName,
                manufacture{
                        name,
                        ...
                },
                ...
        }
}

goods의 code를 사용하여 선택된 goods node에서 시작하여 gql은 중첩된 각 필드로 표시된 간선을 따라 그래프 탐색을 시작한다. 쿼리내 중첩된 goodsName필드를 통해 상품의 이름이 있는 노드로 이동하고, 제조사로 레이블이 지정된 상품의 간선을 따라가 제조사의 노드를 가져와 제조사 정보를 얻어온다.

{
        goods : {
                goodsName:"상품",
                manufacture:[
                        {name:"화승산업"},
                        {name:"고려기공"},
                        ...
                ],
                ...
        }
}

출처: codestates https://codestates.com https://codestates.com

접근의 도식화

3.3. 특징

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

4 REST API와의 차이

4.1. REST API 한계

  • Overfetch: 필요없는 데이터까지 제공해야한다.
  • Underfetch: endpoint가 필요한 정보를 충분히 제공하지 못한다.
  • 클라이언트 구조 변경시 엔드포인트 변경 또는 데이터 수정이 필요하다.

4.2. GraphQL의 차이점

REST API GraphQL
형태 정의와 데이터 요청 방법이 연결 형태 정의와 데이터 요청이 완전히 분리
크기와 형태 서버에서 결정 Resource에 대한 정보만 정의하고, 필요한 크기와 형태 클라이언트 단에서 요청할 시 결정
URI가 Resource를 나타내고 Method가 작업의 유형을 나타냄 GraphQL Schema가 Rsource를 나타내고, Query, Mutation 타입이 작업의 유형을 나타냄
여러 Resource접근시 여러번 요청 필요 한번의 요청으로 여러 Resource에 접근
각 요청은 해당 endpoint에 정의된 핸들링 함수를 호출하여 처리 요청받은 각 필드에 대한 resolver를 호출하여 작업을 처리

4.3. GraphQL 장단점

4.3.1. 장점

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

4.3.2. 단점

  • REST API에 친숙한 개발자의 경우 GraphQL을 학습하는데 시간이 필요하다(이건 모든 새로운 기술스택에 해당)
  • 캐싱이 REST보다 훨씬 복잡: HTTP에선 각 메서드에 따라 캐싱이 구현된다. 하지만 GraphQL에선 POST 메서드만을 이용해 요청을 보내기 때문에 각 메서드에 따른 캐싱을 지원받을 수 없다. 그래서 이를 보완하기 위해 Apollo 엔진의 캐싱과 영속 쿼리 가 있다.
  • 고정된 요청과 응답만 필요할 시 query로 인해 요청의 크기가 RESTful API의 경우보다 더 커진다.

5. 구조

  REST API GraphQL
READ GET Qeury로 요청
Create, Delete POST, DELETE Mutation
UPDATE PATCH, PUT Subscription
model request-response subcription model: publish/subcribe
  • Query: 조회
  • Mutation: 수정
    • Create: 생성
    • Update: 수정
    • Delete: 삭제
  • Subscription: 특정 이벤트가 발생 시 서버가 대응하는 데이터를 실시간으로 클라이언트에게 전송한다. 전통적인 요청-응답 모델이 아닌, 발생/구독 모델을 따른다. 클라이언트가 어떤 이벤트를 구독하면, 클라이언트는 서버 웹소켓을 기반으로 지속적 연결을 형성하고 유지한다. 특정 이벤트가 발생하면, 서버는 대응하는 데이터를 클라이언트에게 푸시한다.

5.1. query 조회

5.1.1. field

// 쿼리
{
        hero {
                name
                friends {
                        name
                }
        }
}

// 결과
{
        "data": {
                "hero": {
                        # 가디언즈 갤럭시 영원하라
                        "name": "피터 퀼",
                        "family": [
                                {
                                        "name": "욘두 우돈타"
                                },
                                {
                                        "name": "가모라"
                                },
                                {
                                        "name": "드랙스"
                                },
                                {
                                        "name": "로켓"
                                },
                                {
                                        "name": "그루트"
                                },
                        ]
                }
        }
}

쿼리와 결과가 정확하게 같은 모양을 하고 있다. GraphQL에서는 필수이다. 서버에 요청했을 때 예상했던 대로 돌려받고, 서버는 클라이언트가 요구하는 필드를 정확하게 안다.

원하는 필드를 중첩하여 쿼리도 가능하여 배열을 반환한다. 관련 객체 및 필드를 순회할 수 있어 endpoint를 만들어 각기 요청을 보내는 대신에 클라이언트가 하나의 요청으로 관련 데이터를 가져올 수 있다.

5.1.2. Arguments

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

// 쿼리
{
        villain(name: "타노스"){
                race
                aliases {
                        name
                }
        }
}

// 결과
{
        "data": {
                "villain": {
                        "name": "타노스",
                        "aliases": [
                                {
                                        "name": "매드 타이탄"
                                },
                                {
                                        "name": "우주에서 가장 강력한 존재"
                                },
                                {
                                        "name": "어둠의 군주"
                                },
                        ]
                }
        }
}

REST API와 같은 시스템은 단일 인수 집합만 전달할 수 있다.

5.1.3. Aliases

필드 이름을 중복해서 사용해 쿼리를 해야될 때에는 별명을 붙여서 쿼리한다.

// 쿼리
{
        ronanVillain(name: RONAN) {
                starsIn
        }
        korathVillain(name: KORATH) {
                starsIn
        }
        nebulaVillain(name: NEBULA) {
                starsIn
        }
}

// 응답
{
        "data": {
                "ronanVillain": {
                        "starsIn":"리 페이스"
                },
                "korathVillain": {
                        "starsIn":"자이먼 혼수"
                },
                "nebulaVillain": {
                        "starsIn":"카렌 길런"
                },
        }
}

5.1.4. Operation name

축약형 구문보다는 모호하지 않게 작성하는 것이 중요하다.

// 쿼리
query VillainAndAliases {
        villain{
                name
                aliases {
                        name
                }
        }
}

// 응답

{
        "data":{
                "villain": {
                        "name": "타노스",
                        "aliases": [
                                {
                                        "name": "매드 타이탄"
                                },
                                {
                                        "name": "우주에서 가장 강력한 존재"
                                },
                                {
                                        "name": "어둠의 군주"
                                },
                        ]
                }
        }
}

query, mutation, subcription, describes 등 쿼리를 약식으로 작성하지 않는 한 operation type은 반드시 필요하다.

5.1.5. Variables

예제는 고정인수이지만, 실지로는 동적인수를 받는 경우가 대부분이다.

query VilliainAndAliases($name: Name){
        villain(name: $name){
                name
                aliases{
                        name
                }
        }
}

operation name 옆에 변수를 $변수 이름:타입 형태로 정의한다. 뒤에 !가 붙는다면 optional로 반드시 타입형태여야한다.

5.2. Mutation 데이터 수정

GraphQL은 조회에 중점을 두지만, 서버측 데이터를 수정도 한다. RestAPI의 Post, PUT 요청과 비슷하다.

mutation CreateVillianPraise($name: Name!, $praise: PraiseInput!){
        createPraise(name:$name, praise:$praise){
                writer
                comment
        }
}

5.3. Schema/Type

가장 기본적인 요소는 서비스에서 가져 올 수 있는 객체의 종류와 포함하는 필드를 나타내는 객체유형이다.

스키마: 민스키는 스키마를 보다 형식화 가능한 측면과 절차적 측면에 더 중요성을 두고 ‘프레임(frame)’이라고 부른다. 러멜하트는 스키마를 ‘기억에 저장된 개념을 나타내기 위한 데이터 구조’라고 정의하며, 앤더슨은 스키마를 ‘추상적인 지식 구조’로 정의한다. 메딘과 러스는 스키마를 ‘이해하는 데 사용되는 일반적인 지식 구조’라고 정의한다. 오늘날의 심리학자들은 스키마를 골격 구조와 같은 것으로 생각하고 있다. 그리고 스키마라는 지식 표상 구조는 경험의 구체적인 속성을 조직하는 데 필요한 틀로 작용한다는 것이 인지심리학자들의 생각이다. 러멜하트에 따르면, 스키마 이론은 지식이 어떻게 표상되는지, 그리고 그러한 표상이 어떻게 지식 활용을 촉진시키는지를 다루는 이론이다. 스키마 이론에 따르면, 배경 지식과 스키마는 구별된다. 이전에 습득한 지식을 독자의 배경 지식(선지식)이라고 부르고, 이전에 습득한 지식 구조를 스키마(복수형 : 스키마타)라고 부른다.

type Mable {
        name: String!
        series: [Movie!]!
}
  • Mable은 객체타입이다. 필드가 있는 타입이고, 스키마에 있는 대부분의 타입은 객체 타입이다.
  • name, series는 Mable타입의 필드이다. name, series는 쿼리의 마블타입 어디서든 사용할 있는 필드이다.
  • String은 내장된 스칼라 타입 중 하나이다. 단일 스칼라 객체로 확인되는 유형이다. 뭐리에서 하위 선택을 가질 수 없다. 스칼라 타입에는 ID, Int도 있다.

컴퓨터 프로그래밍에서 변수(變數, variable) 또는 스칼라(scalar)는 아직 알려지지 않거나 어느 정도까지만 알려져 있는 양이나 정보에 대한 상징적인 이름이다. 컴퓨터 소스 코드에서의 변수 이름은 일반적으로 데이터 저장 위치와 그 안의 내용물과 관련되어 있으며 이러한 것들은 프로그램 실행 도중에 변경될 수 있다. 프로그래밍에서의 변수는 값을 나타내는 문자나 문자들의 집합이며 실행 중인 컴퓨터 프로그램에서, 임의의 값을 저장한 메모리 주소에 대응한다. 수학에서 말하는 변수의 개념과 완전히 일치하지 않을 수도 있다. 컴퓨터 변수의 값은 수학에서처럼 등식이나 공식의 필수적인 부분이 아니다. 컴퓨터 환경에서 변수는 반복적인 과정 안에서 이용할 수도 있다. 이를테면 한 장소의 값을 할당한 뒤 어느 곳에서 사용한 다음 새로운 값으로 다시 할당하고 같은 방법으로 다시 사용할 수도 있다. 컴퓨터 프로그래밍에서의 변수는 긴 이름이 자주 나오며, 어떻게 이용할 것인지에 대한 설명을 나타내는 반면 수학에서의 변수는 짧은 시간 동안 쓰이는 간결한, 한 두 개 문자 이름이다. 컴파일러는 변수의 상징적인 이름을 데이터의 실제 위치로 치환해야 한다. 변수 값, 형, 위치는 일반적으로 고정된 채 유지되는 반면 위치에 저장되어 있는 데이터는 프로그램 실행 도중 변경될 수 있다.

  • !가 붙으면 nullable하지 않고 반드시 값이 들어온다는 의미이다. 반드시 값을 받을 수 있다.
  • []는 배열을 의미한다. 배열에도 !가 붙을 수 있다. !가 뒤에 붙어있어 null값을 허용하지 않으므로 항상 0개이상의 요소를 포함한 배열을 기대할 수 있다

5.4. Resolver

요청에 대한 응답을 결정하는 함수로 query, mutation, subcription과 같은 타입의 실제 일하는 방식인 로직을 작성한다.

// 가데이터
const database = require("./../database")

const resolvers = {
        Query: {
                getVillain: async (_, { name, aliases }) => {
                        database.findOne({
                                where: { name, aliases }
                        }) ...
                ...
                }
        },
        Mutation: {
                createVillain: async (_, { name, aliases, starsIn }) => {
                        ...
                }
        }
        Subscription: {
                newVillain: async () => {
                        ...
                }
        }
};

github qraphql api 예제


참조

graphql: main
나무위키: 스키마
apollographql: apollo-server/graphql-playground
wiki: schema
wiki: scalar
graphql: root fields & resolvers
the-guild: resolvers
apollographql: resolvers
graphql: learn
github: graphql/queries
github: octokit/graphql