Programming/Spring Boot

Spring Boot + GraphQL 기본적인 사용법 정리

Jan92 2022. 10. 11. 00:46
반응형

Spring Boot + GraphQL

REST를 대체한다고? GraphQL(Graph Query Language)란?

시작에 앞서 GraphQL이란 무엇인지 간단하게 살펴보면,

페이스북에서 개발된 쿼리 언어인 GraphQL은 기존에 많이 사용되던 RESTful API와는 다른 형식의 인터페이스입니다.

 

GraphQL API

가장 큰 차이로는 RESTful API가 URL, Method 등의 조합으로 다양한 Endpoint를 가지는 반면, GraphQL은 단 하나의 Endpoint 만으로 데이터를 요청하고 응답받을 수 있는데요.

이 방식을 통해 기존의 RESTful API의 단점인 Over-Fetching, Under-Fetching 문제를 해결할 수 있습니다.

 

/*

Over-Fetching(오버 패칭)

오버 패칭은 클라이언트에서 데이터를 요청했을 때, 실제로 사용되는 데이터 외에 사용되지 않는 데이터들도 함께 불러옴으로써 리소스의 낭비가 발생하는 것을 말합니다.

 

Under-Fetching(언더 패칭)

언더 패칭은 클라이언트에서 화면을 구성하기 위해 데이터를 요청할 때, 하나의 API에서 필요한 데이터를 모두 내려주는 것이 아닐 수 있기 때문에 여러 개의 API에 데이터를 요청해야 하는 것을 말합니다.

 

(HTTP 요청 횟수와 응답 사이즈를 줄일 수 있습니다.)

*/

 

실제로 GraphQL을 적용하고 간단한 기능들을 사용해보면서 기존의 RESTful API와의 차이점을 실감할 수 있었는데요.

기술을 익히는 데는 시간이 걸리겠지만, 익숙해진다면 백엔드 측에서는 API를 만들기가 쉬워질 것 같으며, 프론트엔드 측에서는 구체적으로 필요한 데이터만을 받아올 수 있다는 점이 괜찮아 보였습니다.

대신 클라이언트가 직접 쿼리를 작성, 호출하여 데이터를 반환받아야 하는 만큼 백엔드뿐만 아니라 프론트엔드에서도 학습이 필요하다는 점도 있었습니다.

 

단점으로는 하나의 Endpoint를 사용하기 때문에 HTTP 캐싱이 REST에 비해 복잡하다는 점이 있으며, 고정된 요청과 응답만 필요한 경우에는 query로 인해 HTTP 요청의 크기가 REST보다 커질 수 있다는 점이 있습니다.

또한 아직까지 파일 업로드 방법에 대한 구체적인 명세가 존재하지 않아 직접 구현을 해야 하는 번거로움이 있다고 합니다.

 

 

 

Spring Boot + GrpahQL 사용 방법

// https://mvnrepository.com/artifact/com.graphql-java-kickstart/graphql-spring-boot-starter
implementation group: 'com.graphql-java-kickstart', name: 'graphql-spring-boot-starter', version: '13.0.1'

(gradle 의존성 추가)

 

# GraphQL
graphql.servlet.mapping=/graphql
graphql.tools.schema-location-pattern=**/*.graphqls
graphql.servlet.cors-enabled=true
graphql.servlet.max-query-depth=100
graphql.servlet.exception-handlers-enabled=true

(.properties 설정)

 

servlet.mapping 설정은 GraphQL 요청을 받을 endpoint를 설정하는 부분이며, tools.schema-location-pattern 부분은 resources 디렉터리 내 스키마 파일의 경로를 지정하는 것입니다.

 

 

 

// Post Entity (@Entity 제외 나머지 어노테이션 생략...)
@Entity
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int idx;
    private String title;
    private String contents;
    public String category;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "author_idx", referencedColumnName = "idx")
    private Author author;
}

// Auth Entity (@Entity 제외 나머지 어노테이션 생략...)
@Entity
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int idx;
    private String name;
    private String thumbnail;
}

(Post Entity, Author Entity)

 

예시에 사용되는 Entity로 @ManyToOne 연관관계가 설정되어 있으며, Entity 클래스 외 Repository 인터페이스도 생성합니다.

(@ManyToOne에서 FetchType.LAZY 적용 시 LazyInitializationException : could not initialize proxy가 발생합니다.)

 

 

 

type Post {
    idx: ID!
    title: String!
    contents: String!
    category: String
    author: Author!
}

type Author {
    idx: ID!
    name: String!
    thumbnail: String
}

# The Root Query for the application
type Query {
    getAllPosts: [Post]
    getPost(idx: ID!): Post
}

# The Root Mutation for the application
type Mutation {
    createPost(title: String!, contents: String!, category: String, authorIdx: ID!) : Post
    updatePostTitle(idx: ID!, title: String!) : Post
}

(post.graphqls)

 

GraphQL에서 가장 중요한 부분이 바로 스키마(Schema)인데요. 스키마(Schema)란 타입의 집합으로 정의되며, .graphqls 확장자를 가진 파일로 문서화됩니다.

위의 코드를 살펴보면 GraphQL에서는 스키마 파일을 통해 쿼리에서 사용될 객체를 정의하며, 요청 시 어떤 필드를 사용할지, 반환되는 객체는 무엇이 되는지 등을 정의한다는 것을 볼 수 있습니다.

(resources 디렉터리에 graphql 디렉터리를 생성해주고 그 안에 스키마를 생성합니다.)

 

 

위 스키마에서 보이는 type은 스키마의 핵심 단위인데요.

위 예시에서 Post, Author는 객체 타입이며, 스키마는 대부분 객체 타입으로 이루어져 있습니다. 객체 타입은 0개 이상의 필드로 이뤄져 있으며, 각 필드는 Int, Float, String Boolean, ID 같은 기본 타입(스칼라 타입)으로 표현할 수 있습니다. 

(콜론 ' : '을 기준으로 왼쪽은 컬럼명 오른쪽은 스칼라 타입을 의미하는데, 스칼라 타입 뒤에 붙은 ' ! '는 null 값을 허용하지 않는다는 non-nullable의 뜻을 가지고 있습니다.)

 

스키마에 있는 대부분의 타입은 일반 객체 타입이지만 querymutation 같은 특수한 타입도 있는데요.

RESTful API에 GET, POST, PUT, DELETE가 있는 것처럼 GraphQL에는 Query, Mutaion이 존재하며, Query는 CRUD 중 R(select)에 사용되며, Mutation은 C(insert), U(update), D(delete)에 사용됩니다.

 

/*

type Query에 정의된 getAllPosts: [Post]는 모든 Post 객체를 조회하는 쿼리이며, 요청 시 인자를 따로 받지 않고 반환하는 값은 Post 객체의 배열로 반환함을 정의해놓은 것입니다.

type Mutation에 정의된 updatePostTitle(idx: ID!, title: String!): Post는 Post의 title을 업데이트하는 쿼리이며, 요청 시 idx와 수정할 title을 인자로 받아 새로 저장된 Post를 반환함을 정의해놓은 것입니다. 

*/

 

 

 

@Service
@RequiredArgsConstructor
public class PostResolver implements GraphQLQueryResolver, GraphQLMutationResolver {

    private final AuthorRepository authorRepository;
    private final PostRepository postRepository;

    public List<Post> getAllPosts() {
        return postRepository.findAll();
    }

    public Post getPost(int idx) {
        return postRepository.findByIdx(idx);
    }

    public Post createPost(String title, String content, String category, int authorIdx) {
        //author
        Author findAuthor = authorRepository.findByIdx(authorIdx);

        //post
        Post post = Post.builder()
                .title(title)
                .contents(content)
                .category(category)
                .author(findAuthor)
                .build();
        post = postRepository.save(post);

        return post;
    }

    public Post updatePostTitle(int idx, String title) {
        //post
        Post post = postRepository.findByIdx(idx);

        //update title
        post.setTitle(title);
        post = postRepository.save(post);

        return post;
    }
}

(PostResolver Class)

 

RESTful API에서는 Controller -> Service -> Repository 구조를 통해 데이터를 호출하고 받아왔다면, GraphQL에서는 .graphqls -> Resolver -> Repository 구조를 통해 데이터를 받아온다고 볼 수 있는데요.

 

Resolve는 클라이언트가 요청하는 GraphQL을 처리하여 필요한 데이터를 반환해주는 비즈니스 로직으로, GraphQL의 쿼리를 통해 호출되는 실제 행동이 정의되는 곳입니다.

Spring에서는 GraphQLQueryResolver, GraphQLMutationResolver 인터페이스를 제공합니다.

 

 

 

GraphQL 요청 테스트

getAllPosts

post.graphqls 스키마와 PostResolver 클래스에 정의된 getAllPosts 요청을 보냈습니다.

결과로 받기를 원하는 필드는 idx, title, contents를 요청하였고, 요청한 필드에 대한 데이터만 반환되었습니다.

 

 

 

getAllPosts

마찬가지 getAllPosts 요청인데요. 이번에는 반환받기를 원하는 데이터에 author 객체의 idx와 name을 추가하였습니다.

요청 결과 Post 객체의 idx, title, contents와 author의 idx, name을 반환하는 것을 확인할 수 있습니다.

 

 

 

createPost

post.graphqls 스키마와 PostResolver 클래스에 정의된 createPost 요청입니다.

정의된 것처럼 title, contents, category, authorIdx 값을 넣어 요청을 보냈으며, 데이터가 정상적으로 저장되었습니다. 반환되는 데이터 역시 요청한 데이터만 반환되었습니다.

 

 

GraphQL이 어떤 것인지, 어떻게 사용하는지에 대한 기본적인 내용을 익히는 데는 도움이 되셨으면 좋겠으며, 코드가 있는 github 주소 및 참고 자료는 아래에 링크 남겨놓겠습니다.

잘못된 부분은 댓글 남겨주시면 확인하여 수정하겠습니다. 감사합니다.

 

 

 

< github >

 

GitHub - JianChoi-Kor/GraphQL: GraphQL example

GraphQL example. Contribute to JianChoi-Kor/GraphQL development by creating an account on GitHub.

github.com

 

< 참고 자료 >

 

GraphQL 1장 _ 쿼리어(Query, Mutation, Subscription) 정리

GraphQL이 한글패치해서 제공하는 공식 튜토리얼이 있다. 잘 정리해서 알려주고 있어 매우 도움이 되니 참고하자. https://graphql-kr.github.io/learn/ GraphQL: API를 위한 쿼리 언어 GraphQL은 API에 있는 데이..

ws-pace.tistory.com

 

 

[GraphQL] 4. GraphQL 스키마 정의

Schema-First Development 스키마 우선주의는 디자인 방법론의 일종이다. 개발시 스키마를 우선 개발하는 것이다. 여기서 스키마(Schema)란 데이터 타입의 집합이다. 이를 미리 정의해 두면, 스키마 정의

code-masterjung.tistory.com

반응형