GraphQLとApollo Clientを使った話

MF KESSAI(以下MFK)のフロント担当、井原です。 この記事では、以前のエントリーでいつか別記事に書くと言っていたGraphQLのフロント側実装について年をまたいでしまいましたが書いていきます。

GraphQLについての説明は書きません。

導入経緯

サーバ側でのGraphQL実装#なぜGraphQLかを参照ください(丸投げ)。

Client Libraryの選定

GraphQLのClient Libraryはいくつか存在はしています。 存在はしていますが、Apollo Clientがとても優秀で一択でした。 というのも機能が整っているのはあるのですが、vue-apolloやnuxt用のapollo-moduleなどすでに環境も整っているため他の選択肢がありませんでした。

Apollo Client(以下apollo)は、GraphQLをシンプルにクライアント側で扱うことができるクライアントです。 MFKでは、apollo-moduleを利用することでシンプルどころか、リクエストしている感覚すらない状態で利用できています。

apollo-moduleについて

apollo-moduleはapolloをさらにnuxtで使いやすいように隠蔽したものです。 nuxt.configにapolloの設定を書くだけでapolloが使える状態になります。

実際にクエリを呼び出す方法は2通りあります。 1つは、追加されたインスタンスメソッドである$apolloを呼び出して使う方法。 もう1つは、追加されたコンポーネントオプションのapolloを利用する方法です。 詳細は、 vue-apolloのドキュメントを参照してください。

後者のコンポーネントオプションでの呼び出しが本当に優秀で、この方法で呼び出されたクエリはSmart Queryと呼ばれるものになります。 Smart Queryは、watch対象になり、クエリが変化するたびに再度クエリが実行されます。 また、デフォルトだとインメモリキャッシュが有効なため、同じクエリでのリクエストはキャッシュされ無駄にリクエストを発行することがありません。 このため、例外的な処理以外ではリクエストしていることすら忘れることができます。

apolloとTypeScript

元々型定義ができるGraphQLですから、TypeScriptと相性が良いです。 apolloにはクエリのレスポンスや引数などの型定義ファイルを生成できるcliツールが存在します。

apollo-tooling

apollo codegen:generate --localSchemaFile=schema.graphql --queries=queries/**/*.gql --target=typescript

こんな感じのコマンドで型定義ファイルを生成しています。

apollo周りでの苦労した話

実際に使ってみて特に記憶に残っている話を書きます。文字が多いですが余談なので適当に流してください。

apolloのアップデートで苦労した話

apollo-tooling(当時apollo-cli)は2018/11/8で2.0になりました。 バージョンが上がったことはいいのですが、その際、型定義ファイルのビルド時にローカルファイルのスキーマを指定するschemaオプションが無くなり非常に難儀しました。 同じ差分でendpointというオプションが追加されていて、実際に動いているGraphQLサーバからschemaを取得することを想定していたようです。

この件を通して、公開されているGraphQLサーバからschemaを生成できるのを初めて知りました。 サーバ側の記事でも触れましたが、MFKはschemaからサーバ側のコードを生成しているため、IntrospectionQueryでschemaが取得できるとは思ってもみませんでした。 とはいえ、クライアントビルド時に最新のGraphQLサーバが動いていると確約できる場所もないしそもそもCIでのビルド時どうすりゃいいんだよ、と悩みました。

しばらく2.0にはせずに放置して数週間後確認してみたら、schemaオプションはlocalSchemaFileオプションとして復活したため、無事にアップデートできました。

リレーショナルじゃないデータを引っ張ってきたときに苦労した話

RDBから取得しただけのものではなく、複数のソースからのデータをまとめて新しいデータにして返すRPCをGraphQLに対応させた時の話です。 いくら取ってきても複数のデータが全て同じデータになってしまう問題に直面しました。 RPCのログ、GraphQLサーバのログ、ブラウザでのクエリのレスポンス、どれを見ても正しいデータが返ってきているはずなのに、表示時には全て同じデータになっていました。

原因は、apolloのキャッシュでした。 apolloのキャッシュはクエリの結果内にあるidというプロパティをキーにします。参考

サーバ内でこねくり回して作ったデータをリレーショナルっぽく返しているため、ユニークなidが存在せず、仮の値0を入れて返していました。 その仮値を取得していなければ問題なかったのですが、よりにもよってクエリにidを取得するよう書いてしまい、全てのキーは0となり上書きされ同じ値になってしまったようです。

ちゃんとドキュメント読めよと言う問題ですね…。

GraphQL導入して正直な話

GraphQLはクエリ自体に型付けでき、実装工程でAPI仕様を明記することが強制されるため、サーバ/ブラウザ間での情報共有が簡単にできます。 これは非常に大きな利点だと思います。 また、apolloも非常に優秀なクライアントであり、開発体験を良くしています。

ただ、良くなかったと言うより心残りがあります。 自分が採用したかった点としては、JSONRPCのようにクライアント側でリクエストを組み合わせてまとめてできる、それに型がついてると言う点に惹かれていました。 そのクライアント側でのクエリ組み立てを使えるチャンスが少なかった…。

常に付きまとうn+1問題の懸念により結局全部専用のtypeを作ってそれを取得する形式になってしまったのが悲しい。 githubのAPIを見ているとかなり自由に組み合わせられるので頑張って欲しい(上から目線)。

さいごに

GraphQL使い始めて割とすぐの段階でgRPC-webが正式リリースになりました。 実は直近でもう1つ社内用のアプリが必要になっているため、次はgRPC-webで作ってみようと思っています。

こんな感じでより良い環境を模索しながら仕事したい人ぜひ採用情報からご連絡ください。

よろしくお願いいたします!!!!!!