저희 팀에서 기획한 '오늘의빵' 서비스에서는 백엔드와 프론트엔드 통신에 GraphQL
을 사용합니다. NestJS
와 React Native
에서 GraphQL
을 사용해본 경험은 있지만, Spring boot
와 Flutter
환경에서는 GraphQL
을 사용해본 경험이 없어 실제 프로젝트를 실행하기에 앞서 각 환경에서 GraphQL
을 사용하는 방법에 대해 익혀보려고 합니다.
먼저 Spring boot
환경에서 GraphQL
을 사용하는 방법에 대해 알아봤습니다. Spring boot
에서 GraphQL
을 사용할때 graphql-java-kickstart
을 사용하는 방법도 있지만, Spring for GraphQL v1.0.x
이 릴리즈 되었기 때문에 spring-boot-starter-graphql
를 사용해 보기로 결정했습니다.
저는 spring-boot-starter-graphql
을 연습해보기 위해 간단하게 CRUD로 제작할 수 있는 Todo API를 제작해보았습니다. 그러기 위해 먼저 제공하고자 하는 기능들을 GraphQL Schema
로 표현하였습니다.
### Type ###
type Todo {
id: String!,
title: String!,
content: String,
status: Boolean!,
createdAt: String!,
updatedAt: String!
}
input CreateTodoProps {
title: String!,
content: String
}
input UpdateTodoProps {
id: String!,
title: String,
content: String,
}
### Query ###
type Query {
todo(todoId: String!): Todo!, # Todo 아이템 하나 조회하기
todos: [Todo!]! # Todo 전체 아이템 리스트 조회하기
}
### Mutation ###
type Mutation {
createTodo(props: CreateTodoProps): Todo!, # Todo 생성하기
updateTodo(props: UpdateTodoProps): Todo!, # Todo 내용 수정하기
updateTodoStatus(todoId: String!): Todo! # Todo 완료 상태 수정하기
deleteTodo(todoId: String!): Boolean!, # Todo 삭제하기
}
아직까지는 spring-boot-starter-graphql
을 사용하는 글이 많지 않아서 대부분 공식문서를 참고하였습니다. 가장 먼저 build.gradle
에 의존성을 추가해주었습니다. spring-boot-starter-graphql
을 추가해주면서, Spring for GraphQL
이 제공해주는 GraphQL
관련 기능들을 사용할 수 있습니다.
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-graphql'
다음으로 src/main/resouces
아래에 앞서 정의하였던 GraphQL Schema
을 todo.graphqls
로 저장해주었습니다. 그렇다면 Spring boot
에서 GraphQL
을 사용하기 위한 기본 준비가 끝났습니다.
GraphQL
통신의 endpoint의 역할을 할 Controller
를 정의하였습니다. NestJS와 graphql-java-kickstart
에서는 Resolver
로 표현하는데, spring-boot-starter-graphql
의 문서에서는 Controller
로 표현하여 따라서 Controller
로 생성하였습니다.
spring-boot-starter-graphql
에서 Query
와 Mutation
을 정의하는 것은 생각보다 간단했습니다. Query
에는 메소드에 @QueryMapping
을 붙여주고, Mutation
은 @MutationMapping
을 붙여주는 것만으로도 todo.graphqls
에서 정의한 기능들을 Spring boot
에서 구현할 수 있었습니다.
@Controller
@RequiredArgsConstructor
public class TodoController {
private final TodoServiceImpl todoService;
@QueryMapping
public Todo todo(@Argument String todoId) {
return todoService.getOne(todoId);
}
@QueryMapping
public List<Todo> todos() {
return todoService.getAll();
}
@MutationMapping
public Todo createTodo(@Argument CreateTodoRequest props) {
return todoService.create(props);
}
@MutationMapping
public Todo updateTodo(@Argument UpdateTodoRequest props) {
return todoService.update(props);
}
@MutationMapping
public Todo updateTodoStatus(@Argument String todoId) {
return todoService.updateStatus(todoId);
}
@MutationMapping
public Boolean deleteTodo(@Argument String todoId) {
return todoService.delete(todoId);
}
}
다만 주의할 점은 메소드의 이름이 GraphQL Schema
에서 정의한 이름과 일치해야한다는 것입니다. 하지만 이름만 신경써준다면, NestJS
환경에서 구현하였던 것처럼 쉽게 구현할 수 있었습니다.
이번 예제에서는 GraphQL
의 아주 간단한 기능들만 사용하였지만, 유용한 기능들과 해결해야할 문제들이 더 있습니다. NestJS
에서는 @ResolveField()
로 사용되던 기능이 Spring boot
에는 @SchemaMapping
으로 지원하고 있습니다. 또한 Dataloader
또한 동일하게 지원되고 있습니다.
다음에는 @SchemaMapping
을 사용하여 특정 필드의 로직을 분리하는 방법과, 이때 발생할 수 있는 N+1의 문제를 해결하기 위해 Dataloader
를 사용하는 방법을 익혀보려고 합니다. 😄