graphql-java-toolsを使ってGraphQLサーバーを作ってみる!

GraphQLサーバを作る際には graphql-java-tools を使うと便利らしいので使ってみました。

graphql-java-tools について

graphql-java だけでは冗長になりがちな仕組みを提供してくれるライブラリみたいです。すでにプロジェクトで独自のドメイン似特化したPOJOがある場合に、GraphQLとシームレスに統合できるようになっているんだとか。Javaとライブラリ名にありますが、JVM上なら動作するためKotlinでも使うことができるみたいですね。

GraphQLは関連するライブラリが多いように感じます。Qiitaのこの記事にそれぞれのライブラリの関連性が書かれています。この記事によれば graphql-java-tools はGraphQL関連に関して graphql-java のみに依存しているみたいです。

queryの実装

動作環境は以下の通りです。

> java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)

> gradle -version
------------------------------------------------------------
Gradle 6.0.1
------------------------------------------------------------

Build time:   2019-11-18 20:25:01 UTC
Revision:     fad121066a68c4701acd362daf4287a7c309a0f5

Kotlin:       1.3.50
Groovy:       2.5.8
Ant:          Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM:          1.8.0_191 (Oracle Corporation 25.191-b12)
OS:           Mac OS X 10.15.3 x86_64

graphql-java-tools を試すに当たり、 graphql-spring-boot を使用することにしました。このライブラリは graphql-java-tools も含んでいるのでこちらのライブラリだけを依存先に追加するだけでよいみたいです。 build.gradle.kts の全文は最後の補足に乗せてあります。

GraphQLスキーマは以下になります。

type Query {
    bookMarks: [BookMark!]!
}

type BookMark {
    title: String!
    url: String!
}

上記のスキーマに合わせてQueryの実装をしていくのですが、graphql-java-tools では Resolver というクラスをGraphQLスキーマに対応付けします。 簡単に Resolver の立ち位置をスケッチしてみました。

f:id:bau1537:20200221212124p:plain

Queryでは GraphQLQueryResolver インターフェイスを継承したResolverクラスをGraphQLスキーマに対応付けます。bookMarks() メソッドはGraphQLスキーマbookMarks: [BookMark!]! に対応しています。この仕組があることで、GraphQLスキーマとそれを処理する実装の紐づきが明確になっているように感じます。

package demo.tools.graphql

import com.coxautodev.graphql.tools.GraphQLQueryResolver
import org.springframework.stereotype.Component

@Component
class Query(val bookMarkRepository: BookMarkRepository) : GraphQLQueryResolver {

    fun bookMarks(): List<BookMark> {
        return bookMarkRepository.getAll()
    }

}

この Query クラスを使ってGraphQLインスタンスを生成すれば実装は終わりです。 以下のコードではSchemaParser.newParser() によってGraphQLインスタンスを生成しています。これも graphql-java-tools による機能です。 上記の Query クラスは resolvers() メソッドで渡します。

package demo.tools.graphql

import com.coxautodev.graphql.tools.GraphQLMutationResolver
import com.coxautodev.graphql.tools.GraphQLQueryResolver
import com.coxautodev.graphql.tools.SchemaParser
import graphql.GraphQL
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean

@SpringBootApplication
class DemoApplication {

    @Bean
    fun graphQL(query: GraphQLQueryResolver): GraphQL {
        val graphQLSchema = SchemaParser.newParser()
            .file("schema.graphqls")
            .resolvers(query)
            .build()
            .makeExecutableSchema()
        return GraphQL.newGraphQL(graphQLSchema).build()
    }

}

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

mutationの実装

mutationも上記のやり方とほとんど同じです。 Query の代わりに Mutation クラスを作り、登録します。

まずはGraphQLSchemaにMutationを追加します。

type Query {
    bookMarks: [BookMark!]!
}

type Mutation {
    registerBookMark(title: String, url: String): BookMark!
}

type BookMark {
    title: String!
    url: String!
}

次に対応する Mutation クラスを実装します。

package demo.tools.graphql

import com.coxautodev.graphql.tools.GraphQLMutationResolver
import org.springframework.stereotype.Component

@Component
class Mutation(val bookMarkRepository: BookMarkRepository) : GraphQLMutationResolver {

    fun registerBookMark(title: String, url: String): BookMark {
        val aNewBookMark = BookMark(title, url)
        bookMarkRepository.save(aNewBookMark)
        return aNewBookMark
    }

}

最後に Query を作成したときと同様、 GraphQL インスタンスMutation インスタンスを登録します。

エンドポイントを叩いてみる

実際にサーバーに対してリクエストを投げてみました。リクエスト・レスポンスの送受信には GraphQL IDE を使いました。

では、Mutationによって BookMark を登録してみます。

// リクエスト
mutation registerBookMark($title: String, $url: String) {
  registerBookMark(title: $title, url: $url) {
    title
    url
  }
}

{
  "title": "demoo",
  "url": "http://demo.com"
}
// レスポンス
{
  "data": {
    "registerBookMark": {
      "title": "demoo",
      "url": "http://demo.com"
    }
  }
}

Query で全件取得をしてみます。

// リクエスト
{
  bookMarks {
    title
    url
  }
}
// レスポンス
{
  "data": {
    "bookMarks": [
      {
        "title": "google",
        "url": "https://google.com"
      },
      {
        "title": "demoo",
        "url": "http://demo.com"
      }
    ]
  }
}

補足

今回使用したプロジェクトの build.gradle.kts は以下になります。

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.2.4.RELEASE"
    id("io.spring.dependency-management") version "1.0.9.RELEASE"
    kotlin("jvm") version "1.3.61"
    kotlin("plugin.spring") version "1.3.61"
}

group = "com.graphql-java.tutorial"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:6.0.0")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "1.8"
    }
}