기존 Restful Api 방식의 서버 개발을 주로 하다 GraphQL을 알고 난 이후 GraphQL을 이용한 서버 개발에 흥미가 생기기 시작하여 Spring Boot로 GraphQL 서버 개발하는 법을 정리해 봅니다.
먼저 GraphQL의 정의를 위키 백과에서는 이렇게 설명 하고 있습니다
그래프QL(영어: GraphQL)은 페이스북이 2012년에 개발하여 2015년에 공개적으로 발표된 데이터 질의어이다. 그래프QL은 REST 및 부속 웹서비스 아키텍쳐를 대체할 수 있다. 클라이언트는 필요한 데이터의 구조를 지정할 수 있으며, 서버는 정확히 동일한 구조로 데이터를 반환한다. 그래프QL은 사용자가 어떤 데이터가 필요한 지 명시할 수 있게 해 주는 강타입 언어이다. 이러한 구조를 통해 불필요한 데이터를 받게 되거나 필요한 데이터를 받지 못하는 문제를 피할 수 있다.
Ref https://ko.wikipedia.org/wiki/%EA%B7%B8%EB%9E%98%ED%94%84QL
GraphQL에 대한 설명은 아래에 링크에 잘 나와 있어 참고 하시면 좋을것 같습니다.
Ref https://www.freecodecamp.org/news/so-whats-this-graphql-thing-i-keep-hearing-about-baf4d36c20cf
본론으로 들어가서 Spring Boot로 GraphQL서버를 만드는 법을 알아 봅시다.
먼저 아래의 library를 추가 합니다
// graphql-java-tool
implementation("com.graphql-java-kickstart:graphql-java-tools:5.6.0")
// graphql-java-kickstart
implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:5.10.0")
// graphiql-java-kickstart - graphiql을 사용할 경우 추가
implementation("com.graphql-java-kickstart:graphiql-spring-boot-starter:5.10.0")
// scalar Date를 사용하기 위해 추가
implementation("com.zhokhov.graphql:graphql-datetime-spring-boot-starter:1.5.1")
다음과 같이 회원 정보를 저장한 테이블이 있고 그 회원의 소셜정보들을 저장하는 테이블이 있다고 가정하겠습니다.
member 엔티티는 다음과 같이 작성하였습니다.(Kotlin으로 작성 하였습니다)
@Entity
@Table(name = "member", schema = "test")
@EntityListeners(AuditingEntityListener::class)
data class Member(
@GeneratedValue(strategy = GenerationType.IDENTITY)
val seqId: Long? = null,
@Column(unique = true)
val id: String,
var password: String? = null,
var phoneNumber: String? = null,
var address: String? = null,
@Column(unique = true)
val nickname: String,
@Column(unique = true)
val email: String,
@CreatedDate
var regDate: Date? = null,
@LastModifiedDate
var updDate: Date? = null,
@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "member")
var socialMemberInfoList: MutableList<SocialMemberInfo>? = mutableListOf()
)
SocialMemberInfo 엔티티는 다음과 같이 작성하였습니다
@Entity
@Table(schema = "test", name = "social_member_info")
data class SocialMemberInfo(
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
val seqId: Long? = null,
val providerType: String,
val principal: String,
@CreatedDate
var regDate: Date? = null,
@LastModifiedDate
var updDate: Date? = null,
@ManyToOne(fetch = FetchType.LAZY)
var member: Member? = null
)
그 다음으로 스키마를 만듭니다 resources 폴더 아래의 schema.graphqls 파일을 만든 후 아래와 같이 작성 하였습니다.
scalar Long
scalar Date
type Query {
#회원 seq number로 회원 조회
getMemberBySeqId(
#회원 seq number
seqId: Long!
): Member
}
#회원
type Member {
#회원 seq number
seqId : Long
#회원 ID
id: ID
#휴대전화 번호
phoneNumber: String
#주소
address: String
#닉네임
nickname: String
#이메일
email: String
#등록 일자
regDate: Date
#수정 일자
updDate: Date
#Social 회원 정보 리스트
socialMemberInfoList: [SocialMemberInfo]
}
#소셜 회원 정보
type SocialMemberInfo{
#소셜 계정 제공 사이트
providerType: String
#소셜 계정 principal
principal: String
}
그리고 스키마에 정의한 Query인 getMemberBySeqId의 QueryResolver를 작성 합니다. (Repository의 코드는 생략 하겠습니다.)
@Component
class MemberQueryResolver : GraphQLQueryResolver {
@Autowired
private lateinit var memberRepository: MemberRepository
fun getMemberBySeqId(seqId: Long): Member? {
return memberRepository.findBySeqIdEquals(seqId)
}
}
member type에는 socialMemberInfoList라는 서브필드가 존재 합니다. 서브 필드의 Resolver를 작성 합니다.
@Component
class MemberResolver: GraphQLResolver<Member> {
@Autowired
private lateinit var socialMemberInfoRepository: SocialMemberInfoRepository
fun socialMemberInfoList(member: Member): List<SocialMemberInfo>? {
return socialMemberInfoRepository.findByMemberSeqId(member.seqId!!)
}
}
또는 이미 member 엔티티에 socialMemberInfoList 필드가 있기 때문에 socialMemberInfoList의 Fetch전략을 EAGER로 바꿔주면 됩니다.
@OneToMany(fetch = FetchType.EAGER, cascade = [CascadeType.ALL], mappedBy = "member")
var socialMemberInfoList: MutableList<SocialMemberInfo>? = mutableListOf()
이제 코드 작성은 끝났습니다. 이제 작성한 GraphQL서버가 잘 작동하는지 테스트 해봅니다.
위의 추가한 graphiql-spring-boot-starter library가 있기 때문에 http://localhost:8080/graphiql로 접근시 아래의 화면이 나옵니다.
아래와 같이 쿼리를 작성 후 실행시키면 실제 GraphQL query를 작성한 GraphQL서버에 요청하고 받아온 값을 보여 줍니다.
socialMemberInfoList 서브필드도 잘 조회하는 것을 볼 수 있습니다.
아직 GraphQL에 대한 이해가 부족하여 GraphQL에 대한 설명이 부족하여 코드 작성 위주로 글을 작성 하였습니다.
추후 조금 GraphQL을 공부를 한 후 GraphQL에 대한 설명과 mutation query에 대해 작성 하도록 하겠습니다.
작성 중인 코드는 아래의 링크에 있습니다.