grahpql-java-tools (+ kotlin) を使ってGraphQLサーバーを実装してみる

# この記事は何？

Kotlin + graphql-java-tools を使って、GraphQLサーバーを実装してレスポンスを返すまでの工程をまとめます。

この記事で含むこと  
- 簡単なGraphQLサーバーを構築

この記事で含まないこと  
- N+1問題の対策
- ページングの実装


# graphql-java-toolsについて
GraphQLサーバーとしての機能を提供してくれます。
graphql-spring-boot というツールも内部では、 graphql-java-toolsを使用しています。
GraphQLサーバー自体には、Webアプリケーションサーバーとしての機能は備わっていません。
任意のフレームワークないし、ライブラリを使う必要があります。
この記事では、Ktorを使っていきます。


# 実装の流れ
1. graphqlファイルをにスキーマを定義する
2. RootResolverを実装する
3. Resolverを実装する
4. GraphQLのリクエストを処理するためのHandlerを実装する

### RootResolverとResolverの違いについて
graphql-java-toolsには２種類の `Resolver` の概念があります。(ResolverとはMVCでいうところのControllerのようなものです)

以下のようなGraphQLスキーマを実装することを考えます。
親の `Parent` が 子供の `Child` を持っているデータです。

``` example.graphql
type Query {
  parent: Parent!
}

type Parent {
  id: ID!
  name: String!
  child: Child!
}

type Child {
  id: ID!
  name: String!
}
```

このような Has A 関係を持ったデータを graphql-java-tools で返す場合には、 `RootResolver` と `Resolver` の２種類を実装する必要があります。
それぞれの役割は次のようになります。

**RootResolver:**
Parent が参照された時の処理を実装します。
エンドポイントへのリクエストがトリガーとなって呼び出されます。

**Resolver:**
Parent.Child が参照された時の処理を実装します。
登録した親クラスが呼び出されたタイミングがトリガーとなって呼び出されます。

わかりにくいので例を見て説明します。

`exmple.graphql` で定義した型をKotlinのクラスで表現した時に、以下のようになったとします。

``` kotlin

data class Parent(
    val id: Int, 
    val name: String, 
    val childId: Int
)

data class Child(
    val id: Int,
    val name: String
)

```

実際のClassには `Parent.child` は存在しません。
これをレスポンスとして `Parent.child` を返すには、 `Parent.childId` を使って、 `Child` を紐づける必要があります。
そのために、必要なのが `Resolver` です。
これは、指定したクラスがレスポンスとして返される時に、 `Resolver` そのオブジェクトを受け取ることができます。
したがって `ParentResolver` で `Parent` を受け取ることで `childId` を参照することができるので、 紐づいている `Child` を取得することができます。

**まとめると・・・**
Queryで定義したエンドポイントが参照された時の処理 -> RootResolver
エンドポイントが返す type のプロパティが参照された時の処理 -> Resolver 


# 1. スキーマを定義

実際にサーバーを実装する流れをみてみましょう。

まずは、GraphQLのスキーマを定義していきます。
この記事ではGraphQLの構文については解説していませんので、構文については別の記事を参照してください。

ここでは、簡単なQueryの処理を実装していきます。
任意のディレクトリに sample.graphql というファイルを作成してください。
今回作成するサーバーではこのファイルの定義を読み込ませます。

samplesというエンドポイントでSample型のデータをリストで返すように定義してみました。

`graphql/sample.graphql`

``` graphql
type Query {
  samples: [Sample!]!
}

type Sample {
  id: ID
  name: String
  user: User
}

type User {
  id: ID
  sampleId: ID
}
```

これに対応するデータクラスをKotlinでも定義します。

``` kotlin

data class Sample(
  val id: Int,
  val name: String,
  val userId: Int
)

data class User(
  val id: Int,
  val email: String
)
```

# 2. RootResolverを実装
RootResolverを実装していきます。
エンドポイント samples が呼び出された時の処理を実装します。
本来はUseCaseなどを呼び出しますと思いますが、今回は同じリストを返すように作ります。

`RootSampleResolver.kt`

``` kotlin
class RootSampleResolver: GraphQLQueryResolver {
  fun samples(): List<Sample> {
    return listOf(
      Sample(id = 1, name = "sample1", userId = 1),
      Sample(id = 2, name = "sample2", userId = 2),
      Sample(id = 3, name = "sample3", userId = 3)
    )
  }
}
```

ポイントは2つです。
1. `GraphQLQueryResolver` を継承する。
    今回は `Query` の操作を実装するので、 `GraphQLQueryResolver` を継承しましょう。
    `Mutation` の操作を実装する場合には `GraphQLMutaionResolver` を継承してください。
    両方の処理を実装する場合には、１つの `RootResolver` で `GraphQLQueryResolver` と `GraphQLMutaionResolver` の双方を継承することも可能です。

2. GraphQLで定義したQueryと同じ名前のメソッドを用意する。
    `graphql/sample.graphql` で、 `samples` という Queryのエンドポイントを定義しているので、RootReposolverでも `samples` メソッドを定義する必要があります。
    このとき、GraphQL側で引数を指定している場合には、RootReposolverのメソッドで一致する型の引数を受ける必要があります。


# 3. Resolverを実装
SampleResolverを実装していきます。
Sample.user のプロパティが呼び出された時の処理を実装します。

このSampleResolverは `Sample` クラスがレスポンスとして返却されるタイミングがトリガーになります。
例によってUseCaseは呼び出さず、固定のListから `id` が一致するオブジェクトを返します。

`SampleResolver.kt`

``` kotlin
class SampleResolver: GraphQLResolver<Sample> {

  private val users = listOf(
    User(id = 1, email = "user1@hoge.com"),
    User(id = 2, email = "user2@hoge.com"),
    User(id = 3, email = "user3@hoge.com")
  )
  
  fun user(input: Sample): User? {
    return users.find { it.id == input.userId }
  }
  
}
```

ポイントは２つです。

1. `GraphQLResolver` を継承する。
 Resolverが受け取りたいオブジェクトをジェネリクスに指定して、 `GraphQLResolver` を継承しましょう。(今回の例でいうと `GraphqlResolver<Sample>` を継承してください)

2. graphqlで定義したプロパティと同じ名前のメソッドを用意する
 GraphQLで定義した、 `Sample.user` 参照された時の処理を実装するので、 `user` メソッドを実装します。

# 4. Handlerを実装
先ほど定義した sample.graphql ファイル と Resolver を Handler に登録します。
これによって、GraphQLサーバーでリクエストを処理することができます。

`GraphqlHander.kt`

``` kotlin
class GraphQLHander {

  /**
  * GraphQLのスキーマをビルドします。
  * filePathに定義されているスキーマとResolverを登録します。
  **/
  fun init(): GraphQL {
    val filePath = "graphql/sample.graphql" 
    val schema = SchemaParser.newParser()
      // GraphQLの定義を読み込む
      .file(file)
      // Resolverを読み込ませて、GraphQLのエンドポイントと紐づける
      .resolvers(listOf(RootSampleResolver(), SampleResolver())) 
      .build()
      .makeExecutableSchema()
  }

  /**
  * GraphQLのリクエストを処理します。
  **/
  fun execute(query: String, operationName: String, variables: Map<String, Any>, context: Any): ExecutionResult {
    val graphql = init()
      return graphql.execute(
        ExecutionInput.newExecutionInput()
          .query(query)
          .operationName(operationName)
          .variables(variables)
          .context(context)
          .dataLoaderRegistry(dataLoaderProvider.provideDataLoaderRegistry())
      )
  }
}
```

定義したHandlerの `execute` メソッドに、GraphQLのRequestパラメータを渡すことでGraphQLの処理を行うことができます。

Ktorを使って、 `/graphql` というエンドポイントでGraphQLの処理を行うように実装してみます。

`Routes.kt`

``` kotlin
// pathの指定
@Location("/graphql")
// リクエストで受け取るパラメータの設定
data class GraphQLRequest(val query: String = "", val operationName: String = "", val variables: Map<String, Any> = mapOf())

fun Application.routes() {
  
  val handler = GraphQLHandler()
  
  post<GraphQLRequest> {
      val request = call.receive<GraphQLRequest>()
      val query = request.query
      val operationName = request.operationName
      val variables = request.variables
      val context = ConcurrentHashMap<String, Any>()
      call.respond(
        // GraphQLの処理を実行
        handler.execute(query, operationName, variables, context).toSpecification()
      )
  }
}

```

これで、サーバーを起動すると、 `/graphql` のエンドポイントでGraphQLのリクエストを受け付けられるようになりました :smile: 

# 終わりに
今回はシンプルなデータ構造のGraphQLサーバーを実装しました。
実際のコードは需要があれば載せます。

少しでもあなたの参考になれば幸いです。