이전 글에서 사용하지 않은 인자 전달 및 mutation 작성 방법만 정리하고 끝내겠다.

 

1. 조회 쿼리 인자 전달

 

그림 1. 인자를 함께 받는 서비스 코드

이전의 조회 함수에서 딱 하나 추가되었다. 

메소드 선언부 파라미터 전달부를 보면 @GraphQLArgument annotation이 있다. 

클라이언트에서 전달할 인자의 이름을 등록하는 것이다. 

그림 2. 쿼리 조회 시

인자를 등록하게 되면, graphiql에서 자동 완성으로 인자 입력 구간에서 ctrl + space bar로 인자가 검색되는 것을 알 수 있다. 

 

제대로 실행이 되었다면, 그림 1의 출력 함수에서 인자값을 출력하는 것을 확인할 수 있다. 

 

2. 입력/수정/삭제 쿼리 (mutation)

 

그림 3. mutation 등록

@GraphQLMutation annotation으로 해당 함수가 mutation 기능을 가진다고 명세한다. 

query(조회)와 사실 똑같다. RESTful 방식의 method가 그저 명세인 것 처럼 사실상 query와 mutation의 차이도 명세의 수준이다. 선언해놓고 딴짓 할 수 있다. 

 

그림 4. mutation 실행
그림 5. 실행 결과

query나 mutation이나 정말 결국 똑같다. query라고 특별한게 있고 mutation이라고 입출력에 대단한게 있는 것이 아니라 진짜 API를 명세하는 새로운 방법일 뿐이다. 

1. API 구성 

[Spring boot RESTful + Graphql]

간단한 사용 테스트이기 때문에 DB는 설정하지 않았다. 

 

2. 사용한 라이브러리 (4개)

implementation 'com.graphql-java:graphql-spring-boot-starter:5.0.2'
implementation 'com.graphql-java:graphql-java-tools:5.2.4'
implementation("com.graphql-java-kickstart:graphiql-spring-boot-starter:5.10.0")

implementation 'io.leangen.graphql:graphql-spqr-spring-boot-starter:0.0.4' 

각 라이브러리가 무슨 기능을 가지고 있는지...는 솔직히 안찾아봤다. (이러면 안되는데, 설정 검색해보면서 하나하나 해보니까 까먹었다.)

 

* RESTful API와 Graphql을 동시에 사용하고, graphql 파일을 통한 통신 방식은 읽기 힘들어서 

io.leangen.graphql 라이브러리를 추가했다. 이 객체를 통해 graphql파일이 아닌 자바 객체를 사용해서 graphql api를 작성할 수 있다.

 

* API를 바로바로 사용해볼 수 있도록 com.graphql-java-kickstart 라이브러리를 추가했다. 이건 하단에 스크린샷을 붙일 것이다. 

 

* 처음엔 이게 옳은 방법인가, 너무 가라치는 것 아닌가 고민했는데 이 라이브러리도 Graphql 공식 라이브러리인거 보고 그냥 사용하기로 했다. 

 

3. 서버 구성 

Controller, Service, ServiceImpl 구성 방식의 MVC2 RESTful api를 기본으로 구성하였다.

그림 1. Service 코드
그림 2. 객체

4. 소스코드 

* [그림 1] 에서 Controller와 PostService는 각각 RESTful API endpoint와 서비스 코드 문서화(인터페이스) 객체니까 넘어가겠다. (Graphql을 공부하는 시점에서 RESTful와 MVC2 구조를 사용할 줄 안다고 가정하겠다.)

 

그럼 [그림 2]부터 보자.

그림 3. Post 객체 구성. 하단에는 Getter/Setter가 생략된 상태다. 사진 길어짐

VO(Variable Object) 혹은 DTO(Data Transfer Object)라고도 하는 변수형 객체다. 

특이한 점이 있다면 @GraphQLQuery Annotation이다. 저것이 2번에서 언급한 io.leangen.graphql에 포함된 기능이다. 

Graphql로 Post 데이터의 하위 id/temp/number 변수값들을 질의할 때 사용하게 된다. 쿼리와 자바 변수 사이의 맵핑(Mapping)단계라고 생각하면 된다. 

 

다음은 [그림 1]을 보자.  

그림 4. 서비스 로직 객체.

상단의 @Service annotation이 있는 것을 보면 알겠지만, 서비스 로직이 들어가는 ServiceImpl 객체다. 

그리고 그 아래에 있는 '@GraphQLApi'가 바로 Graphql의 대표적인 장점인 '단일 endpoint'의 기능이다. 

그림 5. 서비스 로직 method. 위에 DB 안썼다고 분명히 말했다. 함수명은.... 그냥 복붙하다가 들어갔다 무시하셈.

[그림 5]에서 가장 먼저 봐야할 것은 @GraphQLQuery annotation이다. 

기능은 이름 그대로 Graphql query 기능이다. (query는 데이터 조회를 위한 루트 타입이다. 맞나?)

query의 명칭(이름)은 "post"고 반환 타입은 자바 객체로 명시된 것 처럼 "Post"다. 

즉 이건

query {

    post{

        id,

        type

    }

}

이라고 요청하면

query { // 조회용 쿼리 

    post{ // [그림 5] name = "post"

        id, // [그림 3] name = "id"

        type // [그림 3] name = "type"

    }

}

로 인식하여 값을 내려주겠다는 것이다. 

 

api를 통한 parameter 전달과 데이터 수정(mutation) 호출은 다음 글에 작성하는 것으로 하고, 일단 돌아가는 것을 넣어두겠다. 

 

라이브러리 설명 구간에 올려놓은 graphiql을 사용할 것이다. 

서버를 실행하게 되면, kickstarter 라이브러리가 알아서 localhost:8080/graphiql 페이지를 생성한다. (페이지는 직접 열어라)

 

그림 6. 이렇게 나온다. 구글에서 예제를 찾아보면 node.js express 환경이 많아서 그런가 시퍼런 페이지가 대부분인데 자바는 하얗더라. 하양이 최고지 시퍼렁 놈들 ㅉㅉ

매우 감사하게도 쿼리의 자동 완성이 매우 잘 지원된다. ctrl + space bar 연타하며 아주 바람직하다고 느끼게 된다. 

[그림 6]에서 우측 상단에 Docs를 눌러보자. 

 

그림 7. Docs(문서) 탐색기

Docs(문서) 탐색기가 나온다. 자바 개발자라면 JavaDocs를 통한 문서 자동 생성을 써봤을 수 있다. 그것과 비슷하다고 생각하면 된다. 

 > ROOT TYPES의 query: Query를 눌러보자. 

그림 8. query api의 목록이 있다!
그림 9. api중 post를 눌러보았다. 

api를 타고 들어가면 하위 객체의 상세 구성을 볼 수 있다. 

그럼 이제 query 를 호출해보자. 

그림 10. 왼쪽이 조회용 쿼리, 우측이 반환 데이터다. 

자동 완성의 위력으로 쉽게 작성할 수 있고, api의 반환값을 쉽게 조회할 수 있다. (아주 좋소!)

 

* 여담 

이런 젠장 써보는게 알아보기 쉬울 것 같아서 구성을 시작했는데 생각보다 시간 개잡아먹음 젠장. 

GraphQL 자체는 단순 문자열이다. (JSON이 key:value 구조를 가지고 있지만 근본적으로는 그냥 문자열인 것처럼!)

기본

POST 요청의 data 부분에 GraphQL 문자열을 넣어 end point로 전송한다. 그러면 GraphQL 쿼리에서 요청한 데이터 구성과 같은 구성의 JSON 객체가 반환된다. 만약 GraphQL 실행 중 오류가 있다면, 반환된 JSON 객체의 내부에 error 필드가 입력되어 반환된다.

1. 루트 타입

루트 타입은 해당 쿼리의 작업 종류를 의미한다. (조회/수정)
- query : 조회
- mutation : 입력/삭제/수정
- subscription : 구독형 조회(조회하고 있는 데이터에 수정사항이 있을 때, 실시간 반영을 한다). websocket 방식으로 소통한다. 

2. 쿼리 인자(query arguments)

Sql에서 사용하던 where절이라고 생각하면 편함.
Primary key 역할을 하는 id 또는 특정 type에 대한 값 비교를 통해 원하는 조건의 데이터를 조작할 수 있음

3. 쿼리 필드(query field)

스칼라 타입(scalar type) - 개발 언어에서 사용하는 변수 선언 타입인 primitive type 과 유사하다고 보면 된다. Int, Float, String, Boolean, 그리고 고유 식별자(ID) 되어있다.
객체 타입(object type) - 스키마에 정의한 필드를 그룹으로 묶어둔 JSON 객체 타입. 객체 지향 언어에서 자주 사용하는 객체형 변수라고 생각하면 될 것 같다. 

 

4. 프래그먼트(fragment)

중복되는 필드들의 집합이 있을 때, 프래그먼트로 묶어 중복을 줄일 수 있다. (안드로이드에서의 프래그먼트와 같은 목적을 가지고 있는 것 같긴 하지만 오히려 c언어의 구조체(struct)가 더 어울리는 말이 아니었을까 생각된다.)

 

*정리하다가 보니 객체 타입과 프래그먼트의 정의가 헷갈린다. 객체 타입은 여러 타입들의 객체를 묶어놓은 '데이터 형'

이지만, 프래그먼트는 쿼리의 중복 구간을 줄이기 위한 표기법이라고 생각하는 것이 맞는 것 같다. 

 

5. 유니언 타입(Union type)

여러 개의 타입들을 한 번에 리스트에 담아 반환하기 위한 타입이다. 

예를 들어 A라는 데이터 하위 목록을 조회할 때, 여러 개의 타입을 하나의 타입 아래에 받을 수 있다. 

A type {

    a type, 

    b type

}

의 구조로 조회를 할 때, a와 b는 내부 필드가 다른 객체 타입이다. 

하지만 사용자가 A type 하나로 a와 b 두 형의 데이터를 보고 싶을 때, 유니언 타입으로 서로 다른 두 가지 타입의 데이터 목록을 붙여서 조회한다. 

설명이 복잡해 보이는데, 사실 DB sql의 union과 같다. 차이점이라면 두 데이터 형을 하나의 목록으로 합칠 때, alias 및 column의 구조를 맞춰줄 필요가 없다는 것 정도다. 

생산성 및 성능 이슈로 sql에서는 가급적 피해야하는 문법인데, GraphQL에서는 어떨지 잘 모르겠다. 계속 보다보면 나오겠지?

 

6. 인터페이스(interface)

개발 언어에서 많이 사용하는 그 인터페이스와 같은 목적/기능이다. 추상 타입이고 유사한 객체 타입을 만들 때 필요한 필드 목록을 모아둔 것이다. 특정 인터페이스를 바탕으로 만들어진 객체 타입은 반드시 해당 필드들을 보유하고 있어야 한다. 

 

7. 인트로스펙션(introspection : 단어 뜻은 내성, 자아성찰)

API 스키마의 세부 사항을 알려주는 기능이다. 

api에서 사용할 수 있는 타입(루트/스칼라/객체) 혹은 특정 타입을 골라서 세부 사항을 조회할 수 있다. 

프래그먼트를 사용한 중복 코드 방지가 가능하다. 

 

8. 추상 구문 트리(abstract syntax tree : AST)

API로 쿼리를 보낼 때, 쿼리 문자열은 추상 구문 트리로 파싱된 후 유효성 검사를 거친다. 

계층 구조의 객체로 쿼리를 표현하는데 사용된다. (쿼리 문자열 -> 트리)

GraphQL은 AST를 횡단하면서(한 바퀴 돌면서) 현재 api스키마와 비교해 유효성 검사를 진행한다. 구문에 오류가 없고 스키마에 필드와 타입이 다 들어있다면 데이터 조회 후 결과 반환을 해주고, 아니면 특정 에러를 반환한다. 

 

* 여러 문법/구조가 있....는데 이거 다 쓸지 의문이다. 1,2,3,4번 정도는 쓸 것 같지만 나머지는 안쓰게 될 삘이다. 

1. GraphQL 정의

- RESTful API와 같은 선상이라고 보는 것이 이해하기 좋다. (정확히는 RESTful의 하위다. POST 통신의 Data 구간에 요청 쿼리를 전달한다.)
- *클라이언트와 서버 간의 통신 명세다.
- 페이스북에서 내부적 성능 개선을 목적으로 만들었다.
- 선언형 데이터 페칭 언어(Declarative Data Fetching Language)다. 어떤 데이터가 필요한지에 중점을 두고, 어떻게 가져올지에 대한 것은 신경쓰지 않는다.


2. GraphQL이 생긴 이유

RESTful API의 단점을 해결하기 위해 만들어졌다. 
단점 1. 오버페칭(Overfetching)
- 클라이언트가 필요로 하는 데이터보다 많은 데이터를 반환 받는 상황을 말한다. 요구사항의 추가 및 여러 페이지에서 1개의 api를 사용하는 경우, RESTful API 서버에서는 필연적으로 발생한다.

 

단점 2. 언더페칭(Underfetching)
- 1개의 웹 페이지에서 필요로 하는 데이터를 준비하기 위해, n개의 api를 호출해야 하는 상황을 말한다.

 

단점 3. RESTful API 서버 엔드포인트 관리 (api 개수 = uri 개수)
- 서버 운영 및 유지 보수 시에 변경/추가 기능이 생기면 api 라우팅을 새로 만들어서 사용하는 경우가 많다. (유사 기능이라고 해도 개발/운영 편의성을 위해 새로 만들어 사용하는 경우가 생각보다 많다.)

위 3가지 단점을 보완하기 위해
1. GraphQL로 필요로 하는 파라미터만을 질의하여, 질의한 데이터 구조 그대로 반환 받는다.
2. 중첩 GraphQL을 작성하여, api를 1회 호출하여 필요로 하는 데이터를 받을 수 있도록 한다.
3. GraphQL 서버는 서버 엔드포인트(라우팅 uri)를 하나로 설계된다. (엔드포인트를 기준으로 호출하지 않기 때문에 가능하다.)

 

*3번의 경우, RESTful API 서버에서는 [도메인 + endpoint]를 기준으로 호출하지만 Graphql은 쿼리 내부에 어떤 데이터를 불러올지를 담기 때문에 uri를 통한 endpoint를 따로 만들어줄 필요가 없다(Graphql로 데이터를 호출하면 바로 이해가 가는 부분이었다).
-> 서버 개발의 생산성면에서 작업을 줄일 수 있다. Controller와 Service를 관리하던 서버 개발자의 작업 중 Controller의 작업을 줄일 수 있게 되었다. (근데 마냥 그렇지만도 않다. 결국 호출 쿼리 이름을 지어줘야 한다.)

* "GraphQL이 RESTful을 망하게하는 것이 아니라 RESTful의 약점을 보완하기 위한 수단"이라고 보는 것이 좋다고 한다. 
처음부터 GraphQL을 기반으로 개발한 경우가 아닌, RESTful api 서버에 GraphQL을 연동한 곳도 많다고 서술되어 있다.

 

* RESTful과 GraphQL 2중 구성으로 만들 수도 있다. 서버 로직의 핵심이 되는 서비스 코드는 동일하기에 접근 방식만 허용해주면 된다. 

+ Recent posts