🥗

GraphQLってすごない?っていう話

2021/06/12に公開

みなさまこんにちは! 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やる場合はSangriaCalibanがあります。

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]])

という風に headyDataIO(必要になったときに実行してデータをとってこれる型だぜ)と宣言するだけで、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