GraphQLのリクエストをapollo-androidとRetrofitでそれぞれ実装してみました。
なのだけれど、実装中にGitHubのGraphQLドキュメントを読んでたら大半書いてあったので、多分こっちを隅から隅まで読んでもらう方がいいブログです。 やっぱり公式ドキュメントとかデファクトスタンダードのライブラリドキュメントを読むの大事。
GraphQL
2020年のAndroid/iOSアプリ開発だと、長くメンテしているアプリだとなかなか置き換えることができませんが、GraphQLがきてます。TwitterもGitHubも、どちらも下記のようなブログ出しているあたりにキテる感をみてます。*1
もちろんみんな大好きFacebook Loginを導入すると、GraphQLのリクエストをすることになります。 もしかすると、意図せず「あのレスポンスからキー名を指定して必要なパラメーターを取得しているアレ」と見ているやつかもしれません。
きっかけ
GraphQLをアプリに導入しようとすると、大体次の記事にたどり着きます。
実際、筆者のプロダクトでも、最初のGraphQLエンドポイントの追加から1年近くたってようやく専用クライアントのApolloを導入したのでした。それまでは、単にJSONをPOSTしてJSON responseを受け取るAPI endpointがひとつ増えただけという運用でした。
自分の環境の場合、導入当初はGraphQLに順々に移行していこう、という話で始まったので最初からApolloを導入しました。
しかし、諸事情ありGraphQLで運用されるAPIが増やせないまま1年半ほど経ってしまいました。 Apollo-AndroidとApollo-iOSは開発が非常に早く、また急激に進んでいくため、メンテナンスやバージョンアップ時のコストが高いライブラリの印象があります。 このこともあり、一時的に脱Apolloをすることとしました。
脱Apolloは、元々GraphQLをスキーマによるクラス定義程度しか利用していなかったため、簡単に行うことができました。ApolloのSQLを利用したキャッシュなどをガッツリ使っている場合には、簡単には行かないと思うので、要件的にラッキーだったと思います。 ただこの呼び替えを実装するにあたり「GraphQLのエンドポイントにRetrofit/URLRequestを利用してリクエストを送る」実装の例が見当たらず、100%手探りの対応をすることになりました。
そんなわけで、やったことの復習がてらサンプルアプリを作りました。
やったこと
GitHub APIにApollo-AndroidとRetrofitから同じ(この表現が難しい)レスポンスを取得するリクエストを投げるサンプルです。*2
GraphQLRepository
GraphQLをApollo-Androidを用いて呼び出しています。
class GraphQLRepository @Inject constructor( private val client: ApolloClient ) { suspend fun repositories() = client .query(RepositoriesQuery()) .await() .data?.viewer?.repositories?.nodes .orEmpty() .filterNotNull() }
これと
query Repositories { viewer { repositories(first: 20) { nodes { ...RepositoryFragment } } } } fragment RepositoryFragment on Repository { id name url description createdAt }
のクエリの組み合わせです。 アクセストークンを生成した「自分の」リポジトリを上から20取得します。
なお、Apolloでは Repositories
のようにクエリに名前をつける必要があるため query Repositories
となります。
ここで自動生成されたコードがどうなるかは……ちょっと長くなるのでcloneして実行してみていただければ。
RetrofitRepository
呼び出しの処理をひとまとめにすると、下記のようになっています。
class RetrofitRepository @Inject constructor( private val service: RetrofitService ) { suspend fun repositories() = service.repositories( GraphQLBody( query = "query { viewer { repositories(first: 20) { nodes { ...RepositoryFragment } } } } fragment RepositoryFragment on Repository { id name url description createdAt }", variables = "{}" ) ).data.viewer.repositories.nodes }
interface RetrofitService { @POST(value = "graphql") suspend fun repositories(@Body body: GraphQLBody): RepositoryResponse }
@JsonClass(generateAdapter = true) data class GraphQLBody( val query: String, val variables: String )
こちらは先述のクエリからクエリに付けられた名前を削除したものとなります。 GraphiQLあたりで作成したクエリをそのまま貼り付けるとこうなる感じですね。
fragment
として定義したRepositoryFragmentをどうやって受け取るかというと、下記のようなJSONをパースするコードが必要になります。
@JsonClass(generateAdapter = true) data class RepositoryResponse( val data: RepositoryData ) @JsonClass(generateAdapter = true) data class RepositoryData( val viewer: RepositoryViewer ) @JsonClass(generateAdapter = true) data class RepositoryViewer( val repositories: RepositoryNodes ) @JsonClass(generateAdapter = true) data class RepositoryNodes( val nodes: List<RepositoryFragment> ) @JsonClass(generateAdapter = true) data class RepositoryFragment( val id: String, val name: String, val url: String, val description: String?, val createdAt: String, )
シンプルではあるのですが、毎回書くとちょっと嫌な気持ちになる量なことが伺えるかなと思っています。
簡単なまとめ
GraphQLで多数のリクエストを送るような処理、もしくは頻繁にデータ構造を変えたいケースではApolloなどのライブラリを利用したくなることが伺えるかなと思っています。 依存するライブラリを削減したかったり、一部からGraphQLを導入する際にはリポジトリを参考にしてもらえれば。
なお、サンプルアプリを動かすにはRepositoryへのアクセス権限を持つPersonal Access Tokenが必要になります。 GitHubのDeveloper Settingsから生成してみてください。