Spring Boot에서
GraphQL
을 사용해보자
개발 환경 정보
관련 링크
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'
...
}
GraphQL의 스키마Schema를 정의 하는 *.graphqls 파일을 생성한다.
resources
폴더 아래에 생성*.graphqls
파일들을 도메인별로 나누어 관리할 수 있다.*.graphqls 파일에 Entity에 해당하는 모델Model, CRUD에 해당하는 쿼리Query 등의 정의를 해본다.
query
타입과 Data를 생성(Create), 수정(Update), 삭제(Delete) 하는 mutation
을 정의한다.schema
키워드를 사용해 선언 한다. schema {
query: Query,
mutation: Mutation,
}
type
키워드를 이용해서 새로운 타입을 생성하고 타입의 내용을 정의 할 수 있다.
Member 엔티티에 대한 GraphQL 객체 타입 Member를 정의 해본다.
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;
}
type Member {
memberSn: Int!,
memberId: String!
}
# ! 는 필수 값(Non-Null)임을 의미, nullable의 경우 !를 제거
query
와 mutation
타입도 type 키워드를 이용해 내용을 정의한다. # === 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!,
}
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);
}
}
GraphQLMutationResolver 인터페이스를 사용하여 GraphQL의 mutation
타입의 메소드들을 구현한다.
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;
}
}
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 {
memberBySn(memberSn: 27) {
memberSn,
memberId
}
}
{
"data": {
"memberBySn": {
"memberSn": 27,
"memberId": "test_id"
}
}
}
query {
memberBySn(memberSn: 27) {
memberId
}
}
{
"data": {
"memberBySn": {
"memberId": "test_id"
}
}
}
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와의 가장 큰 차이라고 생각한다.