GraphQLの型をKotlinの型に変換する
初めに
ポート株式会社 サービス開発部 Advent Calendar 2023 14日目の記事です。
自分が携わっているサービスでは、APIにGraphQLを採用しています。
サーバーから、GraphQLによって取得・更新できるデータを、Androidで加工し、データを表示したり、更新したりしています。
その取得・更新するデータには、型が存在します。StringやIntのようなスカラー型であったり、サービス独自のビジネスロジックが考慮された型であったり。。。
これらのGraphQLの型は、Android Kotlinの型(クラス)にそのまま対応しているものとそうでないものがあるので、対応していない場合は、どのようにKotlinの型に変換すればいいかを紹介していけたらと思います!
GraphQL
まず、簡単にGraphQLについて紹介します。
GraphQLは、Facebookが開発したAPIのためのクエリ言語です。
特徴
GraphQLには以下の特徴があります。
- クライアント側で取得・更新するデータを宣言できる
- エンドポイントが1つしかない
- 型とスキーマが存在する
クライアント側で取得・更新するデータを宣言できる
GraphQLは、クライアント側でデータの宣言をし、宣言されたデータをサーバーが返すようになっています。
例:ユーザーデータの内、ID・名前・有効かどうかの情報を取得する
{
user {
id
name
active
}
}
{
"data": {
"user": {
"id": 1,
"name": "Test User",
"active": true
}
}
}
エンドポイントが1つしかない
REST APIのように、複数のエンドポイントを設けず、単一のエンドポイントを使用します。
そのため、HTTPメソッドは取得も更新も全てPOSTになります。
例:ユーザーデータ取得をコールする
$ curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ user { id user active } }"}' \
https://example.com/graphql
型とスキーマが存在する
型については導入部分で軽く触れた通り、StringやIntのようなスカラー型が存在したり、独自の型を作成できたりします。
それらの型の情報は、スキーマに集約しています。
クライアントはこのスキーマを通じてデータを呼び出します。
type User {
id: Int
name: String
active: Boolean
}
GraphQLのスキーマにも種類があるので、興味のある方は、以下の記事を読んでみてください。
GraphQLの型
GraphQLの型の種類は以下のものが存在します。
- スカラー型
- オブジェクト型
- 列挙型
- 入力型
- Query・Mutation
- サブスクリプション型
- リスト
- null
- インターフェース
- ユニオン型
今回は、スカラー型に注目します。
スカラー型
GraphQLがデフォルトで提供しているスカラー型は以下のものです。
-
Int
: 符号がついた32bitの整数 -
Float
: 符号がついた倍精度の浮動小数点値 -
String
: UTF-8形式の文字列 -
Boolean
: 真偽 -
ID
: 一意の識別子。(値の実態はString)
基本的には、これらの型で表現していくのですが、サービス独自のスカラー型を定義することもできます。
例:日付を表すDate
型
"日付(UTC)"
scalar Date
Apollo Client For Kotlin
Apollo
が開発したKotlinのGraphQLのクライアントライブラリー。
このライブラリーを使用することで、AndroidでもGraphQLを扱うことができます。
(今回は、導入する工程をスキップします。公式ドキュメントを参考に導入してみてください。)
変換
GraphQLでサポートされてる型のKotlinへの変換は、ほとんどは、Apollo Client For Kotlin
が自動で行ってくれます。
- String → String
- Int → Int
- Float → Float
- Boolean → Boolean
- オブジェクト型, 入力型, Query・Mutation → data class
- 列挙型 → enum class
etc...
変換ロジックを独自で組み立てる必要があるのは、サービス独自で作成したGraphQLの型です。
この型をKotlinの型に変換するには、Apollo Client For Kotlin
ライブラリーのCustom scalar
というものを使用します。
今回は、以下の型を変換していきます。
"ISO8601形式の日付"
scalar ISO8601Date
"ISO8601形式の日時"
scalar ISO8601DateTime
"符号がついた64bitの整数"
scalar BigInt
mapping
Apollo Client
を入れ、スキーマを適切な箇所に置き、Gradleで実行すると、スキーマから読み込んだ型をKotlinのクラスに変更してくれます。
Apollo Client
がスキーマの情報を取得し、Kotlinのクラスに変換するとき、Kotlinのどのクラスを使用するかをbuild.gradle.kts
に記載します。
mapScalar
メソッドを使用すると、GraphQLで定義されているスカラー型とKotlinの型・クラスを紐付けすることができます。
また、mapScalarToKotlinLong
メソッドのような、Kotlinの特定の型・クラスに変換してくれるメソッドも用意されています。
(公式のKDocにメソッド一覧があるので、適切なメソッドを使用するようにしましょう。)
apollo {
service("convert_sample") {
mapScalar("ISO8601Date", "java.time.LocalDate")
mapScalar("ISO8601DateTime", "java.time.LocalDateTime")
// BigInt → kotlin.Long
mapScalarToKotlinLong("BigInt")
}
}
Adapter
レスポンスの形式がJSONであるため、実際はJSONの型をKotlinの型・クラスに変換する必要があります。
JSONの型は、String・Number・Booleanの3種類なので、数値と真偽以外の値は基本的に文字列としてデータが渡されます。
この時の文字列データをKotlinに変換するためのAdapterクラスを定義する必要があります。
今回は、ISO8601Date
とISO8601DateTime
にAdapter
クラスを用意してあげる必要があります。(BigInt
はmapScalarToKotlinLong
というLongに変換する専用のメソッドを使用しているため、独自で定義しなくても大丈夫です。)
独自でAdapter
クラスを作成するのもいいですし、Apollo Client
にはよく変換されるクラスのAdapter
クラスを提供してくれているので、そのクラスを使用するのもありです。
Adapter
クラスを作成する場合は、Adapter<T>
インターフェースを継承させ、fromJson
メソッドとtoJsonメソッドを定義する必要があります。
import com.apollographql.apollo3.api.Adapter
import com.apollographql.apollo3.api.CustomScalarAdapters
import com.apollographql.apollo3.api.json.JsonReader
import com.apollographql.apollo3.api.json.JsonWriter
import java.time.LocalDate
class ISO8601DateAdapter: Adapter<LocalDate> {
// JsonからKotlinに変換する
override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): LocalDate {
return LocalDate.parse(reader.nextString(), DateTimeFormatter.ISO_DATE)
}
// KotlinからJsonに変換する
override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: LocalDate) {
writer.value(value.toString())
}
}
Adapter 適用
Adapter
クラスを用意できたら、build.gradle.kts
のmapScalar
メソッドに追加します。もしくは、ApolloClient
を作成するときにaddCustomScalarAdapter
に渡してあげます。
apollo {
service("convert_sample") {
// 作成したAdapterの場合
mapScalar("ISO8601Date", "java.time.LocalDate", "com.example.ISO8601DateAdapter")
// Apolloが提供しているAdapterの場合
mapScalar("ISO8601DateTime", "java.time.LocalDateTime", "com.apollographql.apollo3.adapter.JavaLocalDateTimeAdapter")
}
}
or
ApolloClient.Builder()
.serverUrl("https://example.com/graphql")
.okHttpClient(okHttpClient)
.addCustomScalarAdapter(
CustomScalarType(name = "ISO8601Date", className = "java.time.LocalDate"),
com.example.ISO8601DateAdapter
)
.addCustomScalarAdapter(
CustomScalarType(name = "ISO8601DateTime", className = "java.time.LocalDateTime"),
com.apollographql.apollo3.adapter.JavaLocalDateTimeAdapter
)
.build()
これで、GraphQLの型がKotlinの型に変換されるはずです!
終わりに
GraphQLの型をKotlinの型に変換するのに、そこまで複雑な設定はいらないので、個人的には簡単に実装できるものだと思います。
ただ、実質な変換は、GraphQLの変換ではなく、JSONの変換なので、JSONに対しての知識も必要であると学びました。
また、型の範囲(主に数値系)を把握しておかないと適切な型に変換できないということも今回の対応で気づくことができました。
GraphQLの型をKotlinの適切な型に変換させることで、考慮することが減るので、これから他の型を対応する際もなんの型に変換するのかを吟味する必要がありそうです!
Discussion