サマリー
LISTENアプリのGraphQLに関するエピソードでは、GraphQLの仕組みによってサーバーとクライアント間のデータ取得が効率化される方法が説明されます。また、データの更新がどのように自動化され、そのメリットについても語られています。本エピソードでは、ListenアプリにおけるGraphQL実装をクライアントサイドとサーバーサイドの観点から振り返ります。Lighthouseライブラリの特性や、開発の効率化に関する考えも紹介されています。
GraphQLの基礎
こんにちは、ninjinkunです。
テックボイスアドベントカレンダーの空いている日を埋めようと思ってですね、いくつかネタを用意したんですけど、ありがたいことに、空いている日がですね、全部埋まったので、ネタがですね、中ぶらになってしまったんで、ここでボソボソ喋ることによって供養しようと思います。
前回はですね、LISTENアプリの裏側ということで、Flutterについてお話ししたんですけど、今回はGraphQLについてお話ししようと思います。
GraphQLというのは、サーバーサイドとクライアントサイド、この場合でいうと、LISTENのウェブサーバーとLISTENのアプリとの間の通信を定義するための方式の一つで、こういうのはApplication Programming Interface、APIと呼ぶんですけど、その方式の一つになっております。
このAPIの作り方にもいくつか方法があって、RESTというのが一番一般的な方法で、これはHTTPのセマンティクスに乗っている、非常に昔から使われている方式ですね。
他にも最近はgRPCとかtRPCというやつとか、あとは古くはJSON RPCとか、いろいろAPIを作る方式があってですね、gRPCなんかはサーバー同士の通信によく使われていますし、
tRPCというのは最近たまに聞くんですけど、TypeScriptという言語同士でサーバーとクライアントを書く場合は、なかなか使い勝手がいいらしいです。おそらく型定義とかをうまく使えるんでしょうね。
今回使っているGraphQLというのは何が特徴かというと、グラフというぐらいなのでデータ構造を定義できるんですね。
例えば、ポッドキャストにはエピソードが属していて、そのエピソードにはコメントとかスターとか文字起こしとかが属していますという、そういう従属関係がありまして、UIにもそれが表現されますし、データベースの中にもそれが入っていますというときに、
このグラフQLはそのグラフQLスキーマというデータ定義のファイルにこれを書いておくと、実際に書いた定義に対してサーバーサイドの実装を書いておくと、クライアントからはどのデータが必要かをクライアント側が判断して、それを一気に取得できるということができます。
一気にというのはどういうことかというと、今までの例えばRESTという方式では、このポッドキャストとエピソードとスターというのは別々のリソースとして定義して、一個ずつ取得したりしていたんですね。
なので都合3回とか4回とかのAPIコールが発生すると。そうすると特にモバイルとかでは通信の回数が増えるほど電池を消費するし時間もかかるというので、できる限り通信は1回になる方がいいと。
どうするかというと、この画面にまずデータの定義の仕方として、リソースごとに定義するという方法がサーバーサイドから見ると一番きれいというか、データベースの構造をそのままシャドーしたような設定になるのできれいなんですけど、それをやると複数回コールしないといけないと。
クライアントから見ると、この画面ではこれが必要なんだから全部もらわないといけないでしょうとなると。そうするとRESTの文脈でそれを作ろうと思うと、全部回数APIを作りましょうとなります。
そうすると画面ごとにちょっとずつ要件が違うので、この画面ではこれはいるけどこの画面ではいらないとか、そういう感じで画面ごとにAPIを生やしていく感じになっていくと、作る方も大変だし、その後のメンテも大変になるというのがあって、設計も汚くなっていくので、なかなか悩ましい問題だったんですけど。
なので、サーバーサイドはリソース単位で管理したいんだけど、クライアントサイドは画面に応じて必要なものが変わるという、そういう要件の違いがあります。
グラフQLはここをかなりきれいに解決していて、サーバーサイドはリソース単位で定義を書いておくと、クライアントはそこから必要なものを選んで問い合わせると、必要なものが全部一度に返ってくると。
なので、Podcastにひも付いたエピソード一覧を全部返してくれとか、エピソードにひも付いたスターとかコメントを全部返してくれとか、でもこのページではカバーアート画像はいらないとか、そういうことに非常に柔軟に対応できるようになるというのが、このグラフQLの一番大きな特徴かなと思います。
データ更新の自動化
そういうふうにして、サーバーサイドの要件とクライアントサイドの要件の違いみたいなのを非常にうまく吸収してくれるのがグラフQLという仕組みですね。
私がグラフQLで一番気に入っているのが、これは完全にアプリクライアントから見た視点なんですけど、これは厳密に言うとグラフQL自体の機能ではないんですけど、
結果的にはグラフQLの特性を生かして、クライアントサイドのキャッシュを更新してくれるのを自動化できると。
これはどういうことかというと、これは具体例を出すのが早いと思うんですけど、リッスンで言うと星がつけられますけど、
例えばフォロータイムラインのエピソードからエピソードの個別ページに飛んで星をポチッと押すと、戻ってくると星が消えてますってことが起こると。
なんでかというと、エピソード詳細ページで更新した星の情報が、前のページは既にデータを過去に取得済みなので、こっちのデータはサーバーの状態を最新の状態を反映してないんですよね。
なので星が消えたように見えると。
読み込み直すと星がやってきます。
これは毎回読み込み直せばいいじゃないとなるんですけど、単純なアプリならそれでいいんですけど、リッスンとかSNSアプリみたいにいろんなリンク構造を持っているアプリになると、
エピソードからユーザーに飛んで、別のユーザーのポートキャストに飛んで、そのポートキャストからエピソードに飛んでみたいなね、こういう風にぐるぐるいろんなページに回っていくことができて、
その過程で同じものをまた見ることもあるし、見ないこともあると。
そうなってくると、じゃあ果たしてこの星を更新したらどのページを読み込み直せばいいのかという問題が出ます。
全部読み込み直すと、その都度開いたページ全部に対してリクエストが飛んでしまうので、これは非常にコストがかかるというので、
あとコードとして管理するの大変なので、なかなか悩ましい問題で、私はこれはいいね動機問題と呼んだんですけど、いろんないいねとか星があるようなアプリでは起こりやすい問題です。
GraphQLはどう解決するかというと、これは同じデータですよという風に見出す仕組みが入っていて、それぞれのデータにはIDが振られていて、
あとこのデータはこのオブジェクト型ですよという情報があるんですね。
これをクライアントサイドが管理することで、例えばここで星をつけたエピソードと前のリストで読み込んでいたエピソードは同一だから、
ここで更新した星の情報を前の方にも伝播させようという、そういう内部的な更新ロジックが入っていて、
これを使うとほぼほぼこういう状態の更新を自動化できるというのがあって、作っている方としては単純に更新するだけという、
読み込み直しのことは気にしなくていいという世界観が実現できて、とてもクライアントサイドの実装コストがされやられますと。
これは多分作ってみないとなかなかわからないところだと思うんですけど、そういう非常に大きなメリットがあるので、
私はこの問題に10年ぐらい前から悩まされてきたので、非常に革新的な技術だと思っています。
クライアントライブラリの選択
このキャッシュの更新というのはクライアントライブラリの作りに依存するので、
その辺りがよくできているかどうかとか、キャッシュの更新にもいくつか思想があって、
多分GraphQLそのものではキャッシュの更新についてはあまり定義されていないんじゃないかな。
なのでクライアントライブラリの思想次第なんですけど、
今回リッスンで使っているGraphQL Flutterというライブラリはなかなかよくできていて、この辺りもきっちりやってくれますね。
使い勝手で言うと、ウェブで言うApollo Clientというのが一番メジャーなんですけど、
それともう一方でTurnstack Queryという別のライブラリがあって、
こっちのほうが積極的にいろんなデータの更新を自動化してくれるんですけど、
そっちのTurnstack Queryのほうに近いかなと思います。
そのおかげでかなり開発コストは節約できていますね。
あともう一つ大事なのは、このスキーマの定義と実際にどういうデータが必要かを問い合わせるクエリとか、
更新のデータを定義するミューテーションというのをファイルで書いておくと、
そこから実際の問い合わせのコードを自動生成してくれるというのがいろんな言語にはあって、
このListenのFlutterの場合でもFlutter Code Genというやつを使うと、
GraphQL Code Genか、この辺りを自動生成できて、
GraphQLの定義さえ書いておけば、実際に打った後という言語のファイルは自動で生成されますと。
なので、生成されたファイル、そのコードをユーザーインターフェースにポコッと組み込むと、
もうデータ取得はそれで完成ですというようなことができるので、
これも非常に省力化には役立ちますし、
あとはその過程でいろんな型定義がしっかり入りますので、ミスも減らせるというので、
非常にこの辺りはタイプスクリプトとかいろんな言語で実現されていることなんですけど、
GraphQLのサーバーサイド
Flutterでもできてとてもありがたいなというところがあります。
というのがクライアントサイドから見たGraphQLなんですけど、
一方でサーバーサイドの実装というのもありまして、
私はサーバーサイドはそこまでタッチしてなくて、
最初は近藤さんにやってもらってましたし、今は純木さんに結構やってもらってるんですけど、
自分でサーバーサイドを書くことも多少はあるので、少しそっちにも言及しようと思うんですけど、
GraphQLはサーバーサイドを実現するときも大体ライブラリを入れて何かすることが多いんですが、
これは結構ですね、それぞれの言語とかフレームワークごとにライブラリがあって、
割と使い勝手がちょっとずつ違いますと。
Listsの場合はPHPという言語で書かれており、さらにララベルというフレームワークが入ってます。
ListsのWebはララベルで作られてるんですけど、
このララベルと組み合わせてGraphQLを実現するのに一番のメジャーなのがLighthouseというライブラリになってて、
Listsでもこれを使ってます。
このLighthouseはですね、自分の今までの経験からするとちょっと独特でして、
何かっていうと、あんまりコードを書かなくてもいい設計になっている。
よくあるGraphQLライブラリ、自分が使った範囲だとDGSフレームワークっていうNetflixが作ったJavaとかKotlin用のライブラリとかを使ってたんですけど、
CoreMemoとか他のいくつかのライブラリ、アポロサーバーとか、そういうものもですね、
大体はそのGraphQLのスキーマを書いてから、それに対応するサーバーサイドのコードを自分で書くっていうのが多い方式なんですけど、
このLighthouseの場合は、スキーマにですね、いろんなDecreativeというですね、
Addなんとかみたいなのを書いておくと、自動的にですね、裏側のコードを生成というか、自動的にですね、吉田にやってくれるという仕組みが入っていて、
特にですね、1対1で対応するものがあれば、例えばエピソードっていうものが、すでにラベルの中にもモデルとして定義されているんで、
じゃあこのエピソードっていうタイプをGraphQLで作ると、勝手にですね、これを取得できるようなコードが裏側で解決されますと。
なので、取得するためのサーバーサイドのコードは書かなくていいっていうようなことが実現できて、
とてもね、ライトウェイトというか、コースとしては減らせるというようなことが可能になっているね、なかなかすごいライブラリだなと思いますね。
よく言うと省力化なんですけど、ちょっとね、エンジニアが嫌う言葉で言うとマジカルという言葉があって、
このライトハウスはマジカルでもあるんですよね。
どういうことかっていうと、いろんなフレームワークでいろんなものが解決してしまうんで、
どういう仕組みになっているか、よくわかんないけど使えてしまうという感じだし、
もしそこから外れたことをやろうと思うと、自分で結構弁当をしたり、コードを書かないといけないというので、
まあよしよしなんですよね。
開発初期はすごいスピードが出るけど、いろいろ開発が複雑化してくると困るかもしれない。
でもね、ライトハウスは自分でコードを書いて解決できる仕組みもちゃんと備わってますんで、
結構リッスンでもコードを書いている部分もあります。
今のところはね、自分はライトハウスすごいよくできているなという印象なんで、
リッスンにおいては困ることは全然ないなっていう感じですね。
ただですね、これはライブラリの思想の問題ですけど、
ライトハウスはララベルにべったり依存している形で作られているんで、
GraphQLサーバーの作り方にもいくつかあるんですけど、
データを持っているサーバーから独立させて、中間サーバーとしてGraphQLサーバーを定義して、
例えばいろんなサーバーから集めたデータをまとめてGraphQLとして返すみたいなことをやるような設計もあるんですけど、
ライトハウスは全くそういうことを考えていなくて、もうララベルのサーバーにべったり作ると。
完全に一体化してモノレシックなシステムとして作るように設計されているんで、
そういう分離するような設計には全く向いていないというのがあります。
でも正直リストンの規模ぐらいならべったりで作った方が自分は開発効率が高いと思うので、
今はこれでいいかなと思いますね。
というのが結構ライトハウスは初めて使ったんですけど、なるほどなというのがありました。
でもね、ディクエイティブというのがいっぱいあって、いろんなことができるんですけど、
何ができるかを勉強しないとわからないんで、
スピードは出るんだけど学習コストは結構高いかなと思いましたね。
あと多分ライトハウスでしか使えないんですね、この方式は。
なので、汎用性というとどうかなという感じですけど、今のところはなかなかうまく機能しています。
という感じで、グラフQLのクライアントサイドとサーバーサイドについてお話ししてきました。
言葉で使えるのはなかなか難しい感じですけど、どこまで伝わったかわからないですけど、ありがとうございました。
17:41
スクロール