はじめに
この記事は GraphQL Advent Calendar 2019 - Qiita の2日目の記事です。
昨日はebikenさんの https://blog.ebiken.dev/blog/operating-graphql-server-with-gqlgen/ でした。
2年前、「新しくクライアント向けのAPIサーバーを作る」ことをしていた。
このとき、「REST」と「GraphQL」どちらを採用するか検討し、結局「REST」を採用したのだが、「RESTで保守性の高いAPIサーバーを作るぞ!」と思った時に、「なるほど、こういう点はGraphQLの方が優れているのか」ということがわかってきたので、それについて書く。
この記事では「メリット」のみに言及する
これはRESTを採用したことで、「RESTの欠点が見えてきて、一方で、GraphQLのメリットがわかってきた」記事である。
そのため、GraphQLの「デメリット」については説明しない。デメリットが無いわけではないので注意してほしい。
また、細かい実装のはしない。
GraphQLの一番の強みは「型」
型とは
まず僕の考える「型」の定義について説明する。人によっては「モデル」の方がしっくりくるかもしれないが、この記事では一律「型」と呼ぶことにする。
これを見てほしい。これを「User型」「Video型」と言う。これが「型」である。
type User { id: Int nickname: String } type Video { id: Int author: User title: String }
よく言われるGraphQLのメリット
さて、よく言われるGraphQLのメリットとして以下のようなものがあると思う。
- 欲しいパラメータだけ取得できる
- 複数のリソースを一発で取得できる
しかしこれらはメリットとしてあまり大きくないと個人的には考える。
無駄なパラメータを返さないようにすると、負荷が軽くなるが、その実装コストは高い。GraphQLを使うとそれだけで負荷が軽くなる、といった魔法の技術ではない。また、RESTでもこのような実装は可能である。field=id,title
のようなクエリパラメータをつけることが可能だ。GraphQLのメリットはクエリがわかりやすくなることである。
「複数のリソースを一発で取得できる」については、それと「レスポンスが早い」は全く別物である。下手したら「クエリの中の一部がすごく遅いので全体のレスポンスが遅くなる」ことになり、ページ全体が表示されるのが遅くなったりする。
本当の強みは
GraphQLの本当の強みは「型によるコミュニケーションコストの減少」だと僕は考える。
「コミュニケーションコストの減少」とは、GraphQLサーバーを提供するサーバーサイドの開発チームと、それを利用するクライアントサイドの開発チームの間のコミュニケーションコストの減少である。
「型」とは
GraphQL最大の特徴は、サーバーサイドとクライアントサイドがそれぞれ別の型定義を持つことができる 点であると僕は考える。
ニコニコ動画を例に考えてみよう。
サーバーサイドには Video
と User
の型定義があるとする。サーバーサイドの型定義には「我々はこういうデータを持っていて、これらの要素を返すことが可能だ」という定義になる。
サーバーサイドの Video と User の定義がこうあったとする。
type User { id: Int nickname: String uploadedVideos: [Video] } type Video { id: Int author: User title: String thumbnail: String }
クライアントサイドはこの型のサブセットとなるような型を、ページの表示に必要な要素を考慮したうえで 定義する。
例えばトップページのおすすめ動画を見てみよう。
ここで必要なのが、
- 動画のサムネイル
- 動画のタイトル
- 動画のID(クリックして動画を再生するために必要)
とすると、クライアントサイドは以下のような型を定義する。
type RecommendedVideo { id: Int title: String thumbnail: String }
この型をGraphQLのクエリで投げつけると返ってくる。
次に動画再生ページを考える。
ここで仮に
- 動画のタイトル
- 動画のID(動画を再生するために必要とする)
- 動画投稿者の名前
- 投稿者の他の動画(関連動画に表示するとする)
- 投稿者の他の動画については「サムネイル」と「タイトル」と「動画ID」が必要
とすると、型定義はこうなる。
// 再生中の動画 type PlayingVideo { id: Int author: VideoUploader title: String } // 動画投稿者の情報 type VideoUploader { nickname: String uploadedVideos: [RelatedVideo] } // 関連動画に表示する、投稿者の他の動画 type RelatedVideo { id: Int title: String thumbnail: String }
PlayingVideo と RelatedVideo はサーバーサイドの Video のサブセットで、 VideoUploader は User のサブセットなので、このクエリをサーバーサイドに投げると問題なく返ってくる。
サーバーサイドの一つの型に対して、クライアントサイドはそのサブセットとなる型を複数定義することができる。これが重要なポイントとなる。
なぜコミュニケーションコストが減少するか
REST形式の場合、必ずこんなやり取りが発生する。
- クライアントサイド「動画再生ページで叩くAPIでは、動画の情報とユーザーの情報が必要です。さらにユーザー情報にはその人の投稿動画もまぜてください」
- サーバーサイド「ユーザーの投稿動画はなんの要素を返したらいいですか?」
- クライアントサイド「xxとyyとzzがほしいです」
- クライアントサイド「トップページで表示するおすすめ動画にはユーザーの情報は要りません」
- サーバーサイド「なるほど、わかりました」
- 別のクライアントサイド「こっちはまた別のaa要素がほしいですが、bb要素は要りません」
クライアントサイドのデバイスごとに微妙に要求する型が異なると、サーバーサイドは頭を抱えながら実装することになる。
しかし GraphQL だと
- サーバーサイド「Videoとしてxxxとyyyとzzz、Userとしてはaaaとbbbを返すことができます」
- クライアントサイド「じゃあこのページ用にこういう型とこういう型、こっちのページにはこういう型を定義しよう(クライアントサイドに共有の必要すら無し)」
- 別のクライアントサイド「お、うちのクライアントで利用する要素も入っているな。こういう型を定義しよう」
これが結構でかい。クライアントサイドは「PCブラウザ」「スマホブラウザ」「スマホアプリ(しかもiPhoneとAndroidで別だったりする)」「Amazon Fire TV」... と、近年増加傾向しているので、チームごとにこのやり取りをするとコストが高い。
まとめ
GraphQL は
- サーバーサイドが型を定義し
- クライアントサイドがその型を見ながら必要に応じてサブセット型を定義する
- クライアントの型定義にサーバーサイドが振り回されないのでコミュニケーションコストが減る
ということである。
ここで述べたことは僕がRESTを触って感じた所感であり、事実とはことなることがあります。(異なることがあった場合はコメントやtwitterで「優しく」*1教えてもらえるとありがたいです。)
スターウォーズ
GraphQL 公式ページの例のスターウォーズを最近ようやく分かるようになりました。*2 12月にスターウォーズの新作(?)が公開されるみたいです。