一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

あなたのプロダクトに Apollo Client は必要ないかもしれない

この記事は一休 × 出前館 Frontend Meetup でお話した内容をブログにまとめたものです。

user-first.ikyu.co.jp

speakerdeck.com

GraphQL クライアントと聞いて一番に思い浮かぶライブラリは何でしょうか?

多くの方にとっては Apollo Client ではないかと思います。npm trends を見ても Apollo Client のダウンロード数は urql や relay などほかのクライアントと比べ圧倒的です。

実際、一休でも 一休.com や YADOLINK で Apollo を利用しています。

サービス GraphQL クライアント
一休.com Apollo Clinet
YADOLINK Apollo Client
レストラン座席管理画面 なし (axios)
(新)EC① urql
(新)EC② urql
(新)予約管理画面 Relay

しかし、Apollo Client は 「一番有名だから」という理由で使っていいほど無難なライブラリではありません。どちらかといえば、使い所を選ぶ癖のあるライブラリだと私は考えています。

この記事は一休での採用事例を交えながら GraphQL クライアントの選び方についてお話します。もしかすると、Apollo Client はあなたのプロダクトに合っていないかもしれません。

Apollo Client は複雑

以下の図は主要なクライアントライブラリをバンドルサイズが小さい順に並べたものです。これは正確な指標ではありませんが、バンドルサイズの大きさと、機能の豊富さ・複雑さは比例していると考えると Apollo や Relay が比較的複雑なライブラリだということがわかります。

name minified + gzipped
cross-fetch 2.8kB
graphql-request 7.6kB
urql 8.5kB
@apollo/client 40kB
react-relay 55kB

バンドルサイズを小さくするために、別のクライアントを使えと言ってるわけではないことに注意してください。バンドルサイズも重要ですが、"必要最低限の機能を持っているライブラリを使う" ことが大切です。

さて、GraphQL クライアントの仕事とは何でしょうか。突き詰めると HTTP リクエストを発行してAPIサーバーと通信することです。実は専用のクライアントを使わずとも fetch で GraphQL サーバーと通信ができます。

では cross-fetch のようなシンプルなクライアントと比べ、なぜ Apollo Client はこんなにもバンドルサイズが大きいのでしょうか? 通信以外に Apollo が提供している機能とは何でしょうか。

その答えは公式ドキュメントに書いてあります。

Apollo Client is a state management library that simplifies managing remote and local data with GraphQL.

― Apollo Clientは、GraphQLを使用してリモートデータとローカルデータを簡単に管理できる状態管理ライブラリです。

Apollo Client は "状態管理" ライブラリなのです。

Apollo Client を導入する際は、まず「このアプリケーションに状態管理ライブラリは必要か?」という問いに答えなければいけません。答えがNoであれば Apollo Client は必要ありません。

かつてSPAと状態管理ライブラリはセットでした。どのプロダクトのコードを覗いても必ず状態管理ライブラリが入っていました。しかしグローバルな状態のデメリットが認知された現代では、状態管理ライブラリはアプリケーションにとっても必須のパーツではありません。

Apollo Client が向いているケース

Apollo Client が向いているアプリケーションとはどんなものでしょうか?

Apollo Client は "Mutation が頻繁に発生し、かつ Mutation 後に refetch できない" 性質を持つアプリケーションで真価を発揮します。例えば、Twitter や Instagram、我々が運営しているものだとYADOLINKのようなSNSに向いているでしょう。キーワードは『無限スクロール』です。

YADOLINK

無限スクロールにより複数ページのデータ取得した後、特定のアイテムに Mutation を実行するときを考えてみます。例えば、投稿に「いいね」するという操作です。「いいね」が完了すると、投稿のハートアイコンに色が付き、いいねがカウントアップします。

Apollo Client は Mutation のレスポンスを元に1ラウンドトリップで特定の投稿の値を書き換えます。これは Apollo Client の特徴である、"正規化されたキャッシュ" のおかげです。

mutation {
  likePost(postId: Int!) {
    postId
    likeCount
    isLiked
  }
}

反応速度を少し犠牲にすれば、 Mutation 後にページを丸ごと再取得することで同じことが実現できます。実際、urql の Document Cache では Mutation 後に関連するクエリを再取得することでUIを更新します。

しかし無限スクロールによるページネーションを実装している場合は、現在の状態を復元するために複数ページ分のリクエストを発行する必要があるため、すべてのデータを再取得する戦略が現実的ではありません。

逆に言うと、そもそも Mutation がほとんど発生しないアプリケーションや、Mutation 後にデータの再取得によってUIの更新をすることが許されるケースでは Apollo を使う必要はありません。

一休.com に Apollo Client は必要ないかもしれない

さて、では Apollo Client を採用しているもう一つのアプリケーション、我々の看板サービスである「一休.com」はどうでしょうか?

実は一休.comは Query がメインで Mutation がほとんど使われていません。

ECサイトという性質上、一休上で発行されるリクエストのほとんどは "検索クエリ" です。最も重要な操作である "予約" はもちろん Mutation ですが、予約処理後に別ページへ遷移するので Mutation 後にローカルの状態を更新する必要はありません。他にも、クーポンの獲得・お気に入りの追加といった Mutation がありますが、これらもデータの再取得をすれば十分です。

  • 検索 ... Query
  • 予約 ... Mutation
  • クーポンの獲得 ... Mutation
  • お気に入りの追加 ... Mutation

大は小を兼ねる、という一面もあり Apollo Client は一休.comで必要なユースケース "も" カバーしているため、普段の開発では Apollo を使うデメリット感じることはほとんどありません。

しかしエッジケースにおいて、過分なライブラリを使っているせいでトラブルに巻き込まれることがあります。たとえば、一休.com ではIEからのアクセスに対して、Apollo の Store を fork した自前のキャッシュ機構を使うような実装になっていました。これは Apollo Client のキャッシュの正規化がIE上の特定のデータで非常に時間がかかってしまうためです。ひと月以上の時間をかけて Apollo Client のコードを読み、workaround を実装しました。

また、Apollo のプラグインの対応状況が芳しくないせいで、Nuxt3 へのマイグレーションが遅れてしまっています。

もっと軽量なライブラリを使っていれば、こういったトラブルにも巻き込まれいなかったでしょう。自分たちが必要な "一番ミニマムな実装" を採用することが重要だと学びました。

では何を使えばいいの?

では、Apollo が必要ないというケースではどのクライアントを使えばいいでしょうか?

urql や graphql-request がおすすめです。

Apollo が正規化されたキャッシュを持つのに対して、urql は Document Cache というシンプルなキャッシュ機構を採用している点が特徴です。urql は Mutation の実行後に Mutation の戻り値と同じ __typename を取得している Query をすべて再取得します。少し乱暴なようにも感じますが、この方法で十分というアプリケーションも多くあるはずです。

また、Next.js を使っているなら swr + graphql-request という組み合わせも良いでしょう。graphql-request はもっともシンプルな GrapQL クライアントで、状態管理機能を持ちません。クライアント固有の状態がなく、APIレスポンスのキャッシュとして状態を扱うだけであればこの組み合わせがマッチします。swr は Vercel が作っているだけあって、Next と組み合わせてSSRするのも簡単なので要件によってはこちらを検討してみてください。

複雑なアプリケーションには Apollo を使えばいい?

ここまで、「シンプルなアプリケーションにはシンプルな GraphQL クライアントを使おう」というお話をしてきました。では、複雑なアプリケーションでは Apollo Clinet を使うのが正解なのでしょうか?

実はそうではありません。複雑なアプリケーションの中には Apollo と相性が悪いものも存在します。それはサーバーのAPIキャッシュとは別に、リッチな状態を持つアプリケーションです。例えば、われわれが運営しているアプリケーションだと、レストランの席管理画面などがこれに該当します。

座席管理画面

これは日付ごとに何席を一休レストランに提供するかを管理する "在庫カレンダー" と呼ばれる画面です。カレンダー内で日付を選んで、席毎にその日の提供座席数を決定します。曜日一括操作などもあり、サーバーとすぐに同期しない状態操作が多く存在します。

Apollo にはサーバーと同期する状態の他にローカル固有のデータを操作する機能も提供しています。これは Reduxや Vuex が提供する状態管理と同等のものです。一休.com では一部のこの Local State Management の仕組みを利用していますが...正直なところ Redux, Recoil などの専門の状態管理ライブラリと比べてインターフェースがこなれておらず、あまり使いやすいとは言えません。

Apollo で "ローカルの状態管理もできる" ことは確かですが、複雑な状態管理には素直に状態管理ライブラリを入れることをおすすめします。この場合は GraphQL クライアントで状態管理する必要はなくなるので、Recoil + graphql-request などの組み合わせ検討すると良いでしょう。

上記の座席管理画面では GraphQL クライアントは使わずに axios でAPIサーバーと通信し、Vuex で状態管理を行っています。axios で GraphQL の型の恩恵をあまり受けられていないので...もし作り直すとしたら axios ではなく、 graphql-request を使い、GraphQL Code Generator でコードと型の自動生成を行いたいです。

もう一つのリッチなクライアント、Relay の話

最後に、Apollo Client と並んで高機能な Relay について少し触れます。Relay も正規化されたキャッシュを持つ GraphQL クライアントです。ユースケースとしては Apollo Client とほぼ同じだと考えていいでしょう。

では Apollo ではなく、 Relay を使うべきなのはどういったときでしょうか? 以下のケースでは Relay を検討してもいいでしょう。

  1. コードの自動生成 + Fragment Colocation したい
  2. ページネーションされた要素に対して頻繁に要素の追加・削除を行う
  3. React の Experimental な機能をいち早く試したい

Relay には Relay Compiler というコンパイラが付属しています。コンパイラの仕事はコード内の graphql タグから型情報を含むファイルを自動生成することです。GraphQL Code Generator 相当の働きをしているというとわかりやすいかもしれません。また Relay でアプリケーションを作ると自然と Fragment と Component が一致する Fragment Colocation スタイルになります。

Relay には便利なディレクティブがいくつか追加されていますが、私が注目しているのは @appendNode @prependNode ディレクティブです。これは connection に対する要素の追加を宣言的に行えるディレクティブです。Apollo Client で要素を追加する際は手続き的にはキャッシュを操作する必要がありますが、Relay ではそれらの操作はライブラリ内に隠蔽されます。Facebook で利用されているだけあってSNSを作るのに便利な機能がほかにもあります。

Relay は Meta 製のライブラリだけあって React の実験的な機能の取り込みが早いです。Suspense についても Suspense が Experimental の頃から対応していました。今後も先進的な機能が先取りされる可能性があります。ドキュメントが足りないのが懸念点ですが、トータルでは筋がよく、React エコシステムの未来を反映しているライブラリだと考えています。

一休ではとある新規サービスの予約管理画面を Relay を使って開発中です。 社内向け管理画面のようなシンプルな管理画面にはシンプルな状態管理機構を持つ graphql-rquest / urql が向いていると思います。ただ、この予約画面はレストランの方も使うSaaSのような位置づけの管理画面なのでUXを重視して Relay を採用しています。

YADOLINKはSNSですし、GraphQL Code Generator でのコード生成、Fragment Colocation も採用しているため、現在リリース中のアプリケーションの中ではYADOLINKが一番 Relay と相性が良さそうです。YADOLINKのアプリ版を React Native で作ることを検討しているので、その際は Relay が第一候補です。

結局、何を使えばいいのか

さて、まとめです。

Apollo Client が圧倒的な知名度を持っているので Apollo Client を批判するような内容になってしまいましたがそうではありません。 Apollo Client が向いているアプリケーションもあれば、もっとシンプルなクライアントで十分な場合もあります。

最後に簡単なフローチャートを掲載します。

ECサイトや管理画面には Apollo は too much かもしれません。 Apollo Client や Relay、urql + GraphCache のようなリッチなクライアントはSNSのようなユーザーの Mutation が頻繁に発生するサイトに向いています。

GraphQL クライアントの選び方