graphql-java-toolsのResolverを使ってネストされたPOJOをクライアントに返してみる!

graphql-java-tools のResolverを使ってネストされたPOJOをクライアントに返却してみました。

Resolverについて

graphql-java-tools においてResolverとはGraphQLスキーマに対応したPOJOをクライアントに返却する際にデータを解決するインスタンスです。ライブラリ内ではResolverはインターフェイスとして定義されており、使う側はそれらのインターフェイスを実装することでResolverのインスタンスを作ります。ライブラリ内に定義されているインターフェイスの関係性をざっくり書いてみました。

f:id:bau1537:20200223214319p:plain

前回の記事では Query Resolver と Mutation Resolver を実装する例を載せました。一方、今回はネストされたPOJOのResolverの実装をしてみました。

ネストされたPOJOとは?

"ネストされたPOJO"という表現が適切なのかわかりませんが、あるオブジェクトAが別のオブジェクトBをID参照で関連を持っているときに、GraphQLでAオブジェクトとBオブジェクトを一度のQueryで取得するような場合の事を指します。例えば以下のようなGraphQLスキーマがあったときに...

type Query {
    bookMarks: [BookMark!]!
    categories: [Category!]!
}

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

type Category {
    name: String!
    bookMarks: [BookMark!]!
}

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

CategoryBookMark のクラス定義が以下のように定義され、IDによって参照を保持している場合です。

// CategoryはbookMarksフィールドによってBookMarkへの参照を持つ
data class Category(val id: Int, val name: String, val bookMarks: List<Int>)
data class BookMark(val id: Int, val title: String, val url: String)

このとき、クライアントは以下のようなクエリが発行できます。

query categories {
  categories {
    name
    bookMarks {
      title
      url
    }
  }
}

ここでは単純に Category だけを取得して返却することはできません。なぜならクライアントは Category が持っている BookMark のID値ではなく、 BookMark 自体の情報を求めているからです。graphql-java-tools ではこのような場合に CategoryBookMark の両方を合わせて返却する仕組みを提供しています。

Resolverの実装

このような場合、QueryのもととなるPOJOに対応づく GraphQLResolver を実装した独自のResolverインスタンスを作る必要があります。Category に対するResolverは以下のようになります。このResolverの bookMarks() メソッドは、GraphQLの bookMark フィールドに対応しています。

package demo.tools.graphql

import com.coxautodev.graphql.tools.GraphQLResolver
import org.springframework.stereotype.Component
import java.lang.IllegalStateException

@Component
class CategoryResolver(val bookMarkRepository: BookMarkRepository) : GraphQLResolver<Category> {

    // GraphQL の bookMark に対応
    fun bookMarks(category: Category): List<BookMark> {
        return category.bookMarks
            .map { bookMarkRepository.findById(it) ?: throw IllegalStateException() }
    }

}

Queryに対応しているResolverは前回の記事と同様の形で実装します。

package demo.tools.graphql

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

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

    fun categories(): List<Category> {
        return categoryRepository.getAll()
    }

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

}

このような実装を行うことでネストされたPOJOを返却する事ができます。

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

実際にエンドポイントを叩いてみました。

// リクエスト
query categories {
  categories {
    name
    bookMarks {
      title
      url
    }
  }
}
// レスポンス
{
  "data": {
    "categories": [
      {
        "name": "Most Visit",
        "bookMarks": [
          {
            "title": "google",
            "url": "https://google.com"
          }
        ]
      }
    ]
  }
}