GraphQLってすごない?っていう話
みなさまこんにちは! sugitaniと申します。
これまでニコニコ生放送を作ったり、セプテーニというところでGANMA!というマンガサービス作りつつ開発文化を整えたり、SUGARというところでSUGARという芸能人がライブ配信するだけと思いきや実は双方向で話せたりするサービスを作ってたりします。
今やっていること
SUGARは一段落したので、次の企画にお呼ばれしているんですが、それのサーバもScalaで作ろうとしてます。
SUGARではREST APIで作っていたのですが、実装面でも利用面でもいろいろとダルい点が多く、今回はgRPCかGraphQLを使おうと思い調べたところ、サーバ間の通信はかっちりしたgRPC、クライアントとの通信は融通が利くGraphQLが良さそう、となりました
雑なまとめ
- gRPC - RPC(リモート・プロシージャー・コール)なので当然リモートの関数を透過的に呼べるようにするための素敵フレームワーク
- GraphQL - クエリを投げると良い感じにいろんなデータをまとめて返したり出来るようにするためのやりとりの定義
この記事は GraphQLというかGraphQLをやるライブラリのCalibanが凄かった という話です。
GraphQLって「通信減らせるよね!」ってレベルの話じゃなくない?
GraphQLは
GraphQL を利用すれば、クライアント側から必要な内容だけを問い合わせられるので通信回数が減らせます
という紹介がよくされていて自分も「わかるー、リクエストまとめたいし必要分だけほしいよね?」と思ってましたが実際に触ってみると 「違うわ、これサーバ側にとってのRequest/Response周りのだるさ撲滅ツールだわ」 と思うようになりました。
ScalaではGraphQLやる場合はSangriaとCalibanがあります。
Sangriaの方が比較的有名ですが、SangriaはリリースされたばかりのScala3にはまだ対応中のようなので、対応が終わっているっぽいCalibanの方から調査をはじめました。(Sangriaは @sm0kym0nkeyさんが対応に参加されてる様子)
CalibanはZIO (関数型プログラミングをベースにした非同期・並行プログラミングのためのライブラリ)をベースにしたものでZIOと密接に紐付いてますが、この記事ではいったん忘れることにします。
具体例で説明
例えば User
というものを定義するとして User
の中身にはname(名前)
とheavyData(普段は使わない何かしらの重いデータ)
があるとします。
これの取得をREST APIを作るとき
-
heavyData
は普段は返さないけどフラグを付けたりするとレスポンスに添えられるようになる - あるいは
/1.0/headyData/$userName
みたいに別APIとして取得手段を提供する
みたいなことをやる場合もあると思いますが
- 状況とか引数に応じてheadyDataを取得するしないが分岐したあとで、さらに場合によってはJSON組み立て方法が分岐したりする
- 利便性のため
User
を他のAPIのレスポンスにも含めたくなったときheavyData
出す出さないフラグの伝搬に苦しんだりする
などの辛みに遭遇したりします。(※ので、普通はこんな概念の設計にしないとおもいますが記事のわかりやすさ優先で)
GraphQL + Calibanだったら?
GraphQLだったらheavyData
はほしいときだけ取得できます。GraphQLですから。
headyData
がほしいときにデータを準備する手続きもCalibanなら超楽でスキーマ定義で
case class User(name: String,
heavyData: IO[ExecutionError, Option[UserHeavyData]])
という風に headyData
はIO(必要になったときに実行してデータをとってこれる型だぜ)
と宣言するだけで、GraphQLのクエリでheadyData
がほしいときだけheadyData
取得処理が動くようにできます。
(コードは正しさや実践性より雰囲気が伝わりそうな表現にすることを優先してます)
つまりGraphQL+Calibanだと常にJSON組み立てのつらさがなく、いつでもデータを最小限で用意できます。
すごない?
ZQueryつかうともっと凄い
ZIO忘れるって書きましたが、ZQueryが凄いので説明します。
たとえばUser
に .friends(友人の名前一覧)
みたいなのが生えたとして
Aさん → Bさん,Cさんと友人
Bさん → Cさん,Dさんと友人
Cさん → Aさん,Bさんと友人
Dさん → 友人なし
みたいな関係だとします。
このとき "全員の情報ください、ついでにfriend
の情報もください、とクエリが投げられたとすると、頭悪く実行すると
Aさんの情報取得
友人のBさんの情報取得
友人のCさんの情報取得
Bさんの情報取得
友人のCさんの情報取得
友人のDさんの情報取得
Cさんの情報取得
友人のAさんの情報取得
友人のBさんの情報取得
Dさんの情報取得
みたいな処理をしてレスポンス組み立て、みたいなことになります。
ZQueryを導入すると楽な記述で
A,B,C,Dの情報取得!終わり!
という最適実行に勝手になります。
もうちょい具体的には
case class User(name: String,
heavyData: IO[ExecutionError, Option[UserHeavyData]],
friends: ZQuery[Any,ExecutionError,List[User]])
みたいに必要になったときに実行してデータをとってこれる型だぜ、のところがZQueryになるように定義すると出来ます。
すごない?
恥ずかしながらScalaで関数型プログラミング(FP)に寄せてく行為は "高位関数やモナドが飛び交ってて怖い、正直旨みが割に合わん" と思ってましたが、これのおかげでFPすげえわと思うようになりました。FPじゃないとここまで楽にはならないだろー、と思います。
おわりに
以上、GraphQL/Caliban/ZQuery凄いとおもったんだよ!!!!の記事でした。
Discussion