GraphQLはSchemaに型を記述します。その型には2種類あります。
- Object Type ... 取得できるデータの種類とそのフィールドを定義する型
- Scalar Type ... Object Type のフィールドが最終的に変換される、それ以上分割できない型
イメージとしては Scalar Type が集まって Object Type を形成する感じだと思います。
Scalar Type は事前に用意されているものもありますが、独自に定義する事もできるみたいなので試してみました。
独自の Scalar Type を宣言する
Schemaでの宣言の方法は簡単で scalar {型名}
と書くだけです。作ったSchemaの一部はこんな感じ。Data
とEmail
を定義してQuer
とAuthor
で使っています。
scalar Date scalar Email type Query { authorByEmail(email: Email): Author } type Author { id: ID firstName: String lastName: String age: Int birthDay: Date email: Email }
サーバー側で処理する
では Email という Scalar Type のサーバー側での処理を実装してみます。
Scalar Type ごとにGraphQLScalarType
インスタンスが必要なので作ります。GraphQLScalarType
はnewScalar
メソッドが用意されていて、メソッドチェーンをつかって新しいインスタンスを作れるようになっています。そのメソッドチェーンにはcoercing
メソッドがあるのですが、引数に具体的な Scalar Type の変換処理クラスを渡します。
こんな感じですね。
val emailType: GraphQLScalarType = GraphQLScalarType.newScalar().name("Email").coercing(emailCoercing).build()
Scalar Type の具体的な変換処理はCoercing
インターフェイスを実装したクラスで行います。上記の例ではemailCoercing
というインスタンスがそのクラスに該当します。インターフェイスが実装するメソッドは以下の3つ。
- parseValue ... クライアントが送ってきた変数をJavaの型に変換
- parseLiteral ... クライアントが送ってきたqueryの引数をJavaの型に変換
- serialize ... レスポンスに含める値をシリアライズ
全体のコードはこんな感じ。
package com.graphqljava.tutorial.bookdetails import graphql.language.StringValue import graphql.schema.Coercing import graphql.schema.CoercingParseLiteralException import graphql.schema.CoercingParseValueException import graphql.schema.GraphQLScalarType import org.slf4j.LoggerFactory data class EmailScalarType(val rawValue: String) val emailCoercing = object : Coercing<EmailScalarType, String> { private val logger = LoggerFactory.getLogger("emailCoercing") // Client to Server で値を受け取るときに呼び出される // 変数を変換する override fun parseValue(input: Any): EmailScalarType { logger.info("parseValue {}", input.toString()) when (input) { // 入力された値はダウンキャストして使用する is String -> return EmailScalarType(input) // Coercing用の例外クラスを用いることで、適切なエラーレスポンスになる else -> throw CoercingParseValueException("invalid email value") } } // Client to Server で値を受け取るときに呼び出される // queryの引数を変換する override fun parseLiteral(input: Any): EmailScalarType { logger.info("parseLiteral {}", input.toString()) when (input) { // 入力された値はダウンキャストして使用する is StringValue -> return EmailScalarType(input.value) // Coercing用の例外クラスを用いることで、適切なエラーレスポンスになる else -> throw CoercingParseLiteralException("invalid email value") } } // Server to Client で値を渡すときに呼び出される override fun serialize(dataFetcherResult: Any): String { logger.info("serialize {}", dataFetcherResult.toString()) return when (dataFetcherResult) { is String -> dataFetcherResult is EmailScalarType -> dataFetcherResult.rawValue else -> dataFetcherResult.toString() } } } val emailType: GraphQLScalarType = GraphQLScalarType.newScalar().name("Email").coercing(emailCoercing).build()
あとはemailType
をRuntimeWiring
にセットします。
// 一部のコードは省略 private fun buildWiring(): RuntimeWiring { return RuntimeWiring.newRuntimeWiring() .scalar(emailType) .build() }
以上の実装で Email Scalar Type が使えるようになりました!
このように Scalar Type を独自に作ることができます。また、一般的に使われそうなものは以下のGitHubリポジトリにアップされています。
今回はこの中から日付を表す Date
型をつかってみました。
実際に動かしてみる
実際に通信してみます。DataFetcherの実装は割愛します。
リクエスト
{ authorByEmail(email: "sample@demo.co.jp") { id birthDay } }
レスポンス
{ "data": { "authorByEmail": { "id": "author-1", "birthDay": "1990-01-01" } } }
独自に定義された Scalar Type を使って通信することができました。