신규 서비스에 GraphQL을 도입할 지 고려해보기 위해 학습했던 GraphQL 내용입니다.
6장에는 GraphQL에 대한 개인적인 견해를 작성해보았습니다. 피드백 주시면 감사하겠습니다.
- 함께 작성한 사람 : Front-end Part Juno
- 아래 나오는 모든 예시는 Github Repository에서 확인하실 수 있습니다.
- 추후 보완할 내용
- 보안을 위해 HTTP Method를 직접 정의하여 사용하기도 하는데, GrpahQL에서도 가능한지?
- Swagger&RestDoc처럼 GraphiQL을 개발환경에서만 볼 수 있도록 설정하려면?
- GraphQL 관련 플러그인이 있는가? 해당 플러그인으로 직접 스키마를 작성하지 않을 수 있는지?
GraphQL은 API를 위한 쿼리 언어이며 이미 존재하는 데이터로 쿼리를 수행하기 위한 런타임 입니다.
REST API 는 리소스 기반의 아키텍처입니다.
반면 GraphQL은 단일 엔드포인트를 통해 클라이언트가 필요한 데이터의 형태로 요청할 수 있습니다.
REST API 의 문제점인 오버패칭과 언더패칭을 해결할 수 있습니다.
오버패칭 → 클라이언트가 필요로 하는 데이터보다 더 많은 데이터를 요청하는 것
언더패칭 → 클라이언트가 필요로 하는 데이터보다 더 적은 데이터를 요청하는 것
GraphQL은 스키마를 통해 각 필드의 타입을 명시하며, 기본적으로 객체 형식을 사용합니다.
// Request
{
hero {
name
appearsIn
}
}
// Response
{
"data": {
"hero": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
}
}
}
객체 타입과 필드
type Post {
id: ID
title: String
content: String
views: String
author: User
comments: [Comment]
}
Int
: 부호가 있는 32비트 정수.Float
: 부호가 있는 부동소수점 값.String
: UTF-8 문자열.Boolean
: true
또는 false
.ID
: 자주 사용되는 고유 식별자를 나타냅니다.전통적인 REST API 와 비교하면 GraphQL 의 캐싱은 더 복잡합니다.
HTTP caching 은 메소드 별로 캐싱을 지원합니다.
하지만 GraphQL 에서는 항상 POST 메서드만을 이용해 요청을 보내기 때문에 각 메소드에 따른 캐싱을 지원받을 수 없습니다.
이에 대응하기 위해 GraphQL에서는 GraphQL 만의 캐싱 방식을 찾아야 합니다.
대표적인 방법으로 Apollo Client 라이브러리의 cache 를 사용할 수 있습니다.
GraphQL 을 사용 함에도 불구하고 아래와 같은 문제들로 성능 문제가 발생할 수 있습니다.
post(id: $postId) {
id
title
content
views
author {
id
name
phoneNumber
}
comments { // 댓글을 조회한 뒤
id
content
writer {
id
name
phoneNumber
}
post {
id
title
views
author {
id
name
}
comments { // 게시글 조회할 때도 댓글을 중복 조회
id
content
writer {
id
name
}
}
}
}
}
}
GraphQL에서 공식적으로 지원하는 문서화 기능은 GraphiQL Docs 기능이다.
{serverUrl}:{serverPort}/graphiql
Schema
resources/graphql/schema.graphqls
로 작성하곤 합니다.Type System
Spring Boot의 경우, 아래와 같이 스키마 파일 안에 타입을 명시할 수 있습니다.
viewAllPost
와 같은 명칭은 리졸버의 메서드명과 반드시 동일해야 합니다.type Query {
"""
모든 게시글 조회하기
"""
viewAllPost: [Post]
}
type Mutation {
"""
게시글 단건 조회 (조회수 상승 로직이 있어 Mutation으로 구분)
"""
viewPost(postId: ID): Post
"""
게시글 저장
"""
savePost(request: RequestPost!): Post
"""
댓글 저장
"""
saveComment(postId: ID, request: RequestComment!): Comment
}
GraphQL은 쿼리를 확인할 수 있도록 GraphiQL이라는 GUI를 제공한다.
spring:
graphql:
graphiql:
enabled: true # default : false
path: /graphiql # default : /graphiql
전체 게시글 조회 : query
query {
viewAllPost {
id
title
content
author {
id
name
}
}
}
특정 게시글 조회 : mutation
mutation {
viewPost(postId: 1) {
id
title
content
author {
id
name
}
comments {
id
content
writer {
id
name
}
}
}
}
게시글 작성 : mutation
mutation {
savePost(request: {title: "hi title", content: "hi content"}) {
title
content
}
}
댓글 저장 : mutation
mutation {
saveComment(postId: 1, request: {content: "hi content"}) {
id
content
}
}
결론적으로 아래와 같은 이유로 GraphQL이 REST API를 대체하기에는 무리가 있다고 생각이 듭니다.
//전체 게시글 조회 타입 정의
interface QueryResult {
viewAllPost: Post[];
}
//게시글 타입 정의
interface Post {
id: number;
title: string;
content: string;
comments: Comment[];
}
(ex. api/v3)
을 명시할 수 있는 반면, GraphQL은 언제 어떤 기능이 추가되었는지 추적하기 어려울 것으로 예상합니다.type Post {
id: ID
title: String
content: String
views: String
author: User
comments: [Comment]
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class PostEndpointTest {
@Autowired
private WebTestClient webTestClient;
@Test
public void viewAllPost() {
// given
final String requestUrl = "/graphql";
final String query = """
query {
viewAllPost {
id
title
comments {
content
}
}
}
""";
// expected
webTestClient.post()
.uri(requestUrl)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(query)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.data.viewAllPost").isArray();
}
}