[SpringBoot] GraphQL을 써보자

Jung Ian·2021년 10월 10일
1
post-thumbnail

Spring Boot에서 GraphQL을 사용해보자


차례


  1. 시작하기 앞서
  2. GraphQL 환경 설정
    2-1) Dependency
    2-2) Schema 파일 설정
    2-3) Schema 정의
  3. Resolver
    3-1) QueryResolver
    3-2) MutationResolver
  4. API 호출
    4-1) Query API
    4-2) Mutation API
  5. 마무리

시작하기 앞서



GraphQL 환경 설정


Dependency

GraphQL을 사용하기 위한 의존성Dependency들을 설정해본다.

    /**
     * Gradle 설정
     * build.gralde 파일
     */
    dependencies {
        ...
        // GraphQL
        implementation 'com.graphql-java:graphql-spring-boot-starter:5.0.2'
        implementation 'com.graphql-java:graphql-java-tools:5.2.4'
        ...
    }

Schema 파일 설정

GraphQL의 스키마Schema를 정의 하는 *.graphqls 파일을 생성한다.

  • Entity 또는 쿼리의 대상이 되는 객체나 메소드에 해당하는 GraphQL의 정의가 필요하다.
  • GraphQL에서 정의하는 내용(스키마Schema)을 정의한 파일이 *.graphqls 파일이다.
    • 위치 : resources 폴더 아래에 생성
  • *.graphqls 파일들을 도메인별로 나누어 관리할 수 있다.
    • ex) members.graphqls, posts.graphqls, order.graphqls, ..., etc

Schema 정의

*.graphqls 파일에 Entity에 해당하는 모델Model, CRUD에 해당하는 쿼리Query 등의 정의를 해본다.

> query & mutation

  • GraphQL의 진입점EntryPoint를 정의하는 특별한 타입들
  • Data를 조회(Read)하는 query 타입과 Data를 생성(Create), 수정(Update), 삭제(Delete) 하는 mutation 을 정의한다.
    • CRUD에 해당하는 스키마를 정의하며, 특히 query 타입은 필수이다.
  • schema 키워드를 사용해 선언 한다.
    schema {
        query: Query,
        mutation: Mutation,
    }

> type

  • type 키워드를 이용해서 새로운 타입을 생성하고 타입의 내용을 정의 할 수 있다.

  • Member 엔티티에 대한 GraphQL 객체 타입 Member를 정의 해본다.

    • 엔티티에 해당하는 필드의 자료형과 맞춰 GraphQL 객체 타입을 정의해 준다.
  • Member 엔티티 (Java)

    @Setter
    @Getter
    @NoArgsConstructor
    @Table(name="member")
    @Entity
    public class Member {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "member_sn", nullable = false, precision = 15, scale = 0)
        private Long memberSn;

        @Column(name = "member_id", nullable = false, length = 100)
        private String memberId;
    }
  • GraphQL 객체 타입 Member
    type Member {
        memberSn: Int!,
        memberId: String!
    }
    # ! 는 필수 값(Non-Null)임을 의미, nullable의 경우 !를 제거
  • schema 키워드를 이용해 선언한 querymutation 타입도 type 키워드를 이용해 내용을 정의한다.
    • Query와 Mutation 타입은 필요한 추상 메소드로만 구성된 자바의 인터페이스의 형태를 하고 있다.
    # === query type ====

    type Query {
        memberBySn(memberSn: Int!): Member,
    }
    # memberBySn : Query타입의 필드명 (메소드의 역할)
    # (memberSn: Int!) : Int형의 memberSn이라는 파라미터가 필수
    # Member! : 아래의 Member 타입으로 return 값을 반환

    type Member {
        memberSn: Int!,
        memberId: String!
    }
    # === mutation type ===

    type Mutation {
        createMember(saveMemberInfo: SaveMember!): Member!,
        deleteMemberBySn(memberSn: Int!): Boolean!
    }

    type Member {
        memberSn: Int!,
        memberId: String!,
    }

    # createMember(saveMemberInfo: SaveMember!): Member! 의 파라미터
    input SaveMember {
        memberId: String!,
        memberPw: String!,
    }


Resolver


QueryResolver

  • GraphQLQueryResolver 인터페이스를 사용하여 GraphQL의 query 타입의 메소드들을 구현한다.
    type Query {
        memberBySn(memberSn: Int!): Member,
    }
  • member(memberSn: Int!): Member! 의 기능을 Member getMember(Long memberSn)로 구현한다.
    @RequiredArgsConstructor
    @Component
    @Transactional(readOnly = true)
    public class MemberQueryResolver implements GraphQLQueryResolver {

        private final MemberRepository memberRepository;

        /**
        * Get One Member Data
        * GraphQL Schema Query : member(memberSn: Int!): Member!
        * @param memberSn
        * @return
        */
        public Member getMemberBySn(final long memberSn) {
            if (memberSn <= 0)  return null;

            return this.memberRepository
                .findById(memberSn)
                .orElse(null);
        }
    }

MutationResolver

    type Mutation {
        createMember(saveMemberInfo: SaveMember!): Member!,
        deleteMemberBySn(memberSn: Int!): Boolean!
    }
  • createMember(saveMemberInfo: SaveMember!): Member! 의 기능을 Member createMember(Member member)로 구현한다.
    @RequiredArgsConstructor
    @Component
    @Transactional(rollbackFor = Exception.class)
    public class MemberMutationResolver implements GraphQLMutationResolver {

        private final MemberRepository memberRepository;

        /**
        * Save One Member Data
        * GraphQL Schema Mutation : createMember(saveMemberInfo: SaveMember!): Member!
        * @param member
        * @return Member
        */
        public Member createMember(Member member) {
            return this.memberRepository.save(member);
        }
    }
  • deleteMemberBySn(memberSn: Int!): Boolean! 의 기능을 boolean deleteMemberBySn(final Long memberSn)로 구현한다.
    @RequiredArgsConstructor
    @Component
    @Transactional(rollbackFor = Exception.class)
    public class MemberMutationResolver implements GraphQLMutationResolver {

        private final MemberRepository memberRepository;

        /**
        * Delete One Member Data
        * GraphQL Schema Mutation : deleteMemberBySn(memberSn: Int!): Boolean!
        * @param memberSn
        * @return boolean
        */
        public boolean deleteMemberBySn(final Long memberSn) {
            if (memberSn <= 0)  return false;

            Member member = this.memberRepository.findById(memberSn).orElse(null);
            if (member == null) return false;

            this.memberRepository.deleteById(memberSn);
            return true;
        }
    }


API 호출


  • 참고
    • Post 메소드 : 쿼리 문자열을 Http Body로 전송하기 위함
    • "Content-Type: application/json" : 쿼리를 JSON 포멧으로 전송
    • Curl 명령어
          curl -X POST -H "Content-Type: application/json" -d '{ "query": "{ memberBySn(memberSn: 27) { memberSn, memberId } }" }' localhost:8080/graphql/

Query API

  • 쿼리의 결과로 받고 싶은 필드명을 선택할 수 있다.
  • 결과 전체를 가져올 경우
    query {
        memberBySn(memberSn: 27) {
            memberSn,
            memberId
        }
    }
    {
        "data": {
            "memberBySn": {
                "memberSn": 27,
                "memberId": "test_id"
            }
        }
    }
  • 결과 중 항목을 선택하여 가져올 경우
    query {
        memberBySn(memberSn: 27) {
            memberId
        }
    }
    {
        "data": {
            "memberBySn": {
                "memberId": "test_id"
            }
        }
    }

Mutation API

  • 데이터 저장 쿼리
    • 저장 결과로 받은 항목도 선택하여 가져올 수 있다.
    mutation {
        createMember(saveMemberInfo: {
            memberId: "test_id",
            memberPw: "test_pw"
        }) {
            memberSn,
            memberId
        }
    }
    {
        "data": {
            "createMember": {
                "memberSn": 28,
                "memberId": "test_id"
            }
        }
    }
  • 데이터 삭제 쿼리
    mutation {
        deleteMemberBySn(memberSn: 27)
    }
    {
        "data": {
            "deleteMemberBySn": true
        }
    }


마무리


CRUD 기능 별로 메소드를 나눠 각각의 엔드포인트를 가지는 RestApi와 비교했을 때, 확실히 하나의 엔드포인트로서 다양한 처리를 할 수 있기에 편리했다.
필요한 기능과 데이터의 요청을 서버가 아닌 클라이언트 쪽에서 주도적 으로 할 수 있는 것이 RestApi와의 가장 큰 차이라고 생각한다.

profile
Tokyo Dev

0개의 댓글