GraphQLを勉強した
自分でGraphQLサーバーを実装しながら勉強したログ。間違ってるかも。
コードはここにあるが、何の注釈もない。 https://github.com/mizchi-sandbox/play-graphql-server
RESTの課題
REST は URI とモデルのマッピング構造だが、往々にしてクライアントで必要となる構造は モデルのうち一部であったり、そのリレーショナルな構造に依存する。 つまり、REST というルールに従って必要なデータを組み立てると、リレーショナルな構造によってN回のリソースへのアクセスと、興味がないデータを含んだ不要なペイロードが発生しがちである。
GraphQL は何をしたいか
- 1リクエスト内でモデルへの問い合わせを合成し、さらに必要なものだけ返却したい
- 言語とは独立した、転送経路上のモデルの定義を行いたい
パフォーマンス上の理由とセマンティクスが同居しているが、不適当な要求ではない、と思う。
GraphQL の解決アプローチ
GraphQLサーバーは、まずモデルのプロパティと、他のモデルとの関係のグラフ構造を定義する。次に、クライアントに対して公開するクエリを定義する。
GraphQLクライアントは、使うクエリの選択と、その絞込みクエリの2つを同時に送信する。
クライアントからクエリを受けたGraphQLサーバーは、クエリ定義にそって、なんらかの方法で実装されたモデル抽出関数などを叩き、そこで得られたモデルを、定義にしたがって合成し、絞込クエリによってフィルタし、返却する。
URL的には、シングルエンドポイントを持ち、そこに向けてクエリを発行する。モデルの合成は1リクエスト内で行われる。RESTリソース的なセマンティクスは、GraphQLは興味がない。
GraphQLは何をしないか
- バックエンドの実装
- データの通り道のスキーマを定義しているだけ
- なんらかの言語の実装によって自分で書く必要がある
- 最終的なペイロードに載せるデータのフィルタリングはGraphQL側が行うが、その組立は自分でやる必要があるため、その過程にパフォーマンスの劣化は当然発生する。たとえばRubyバックエンドで ActiveRecord の
to_hashなんらかのシリアライズ(追記: ActiveRecord::Baseにto_hashはない、とのこと)で深いリレーショナルのプロパティを掘り出してしまったら、そのSQLコストは発生する。
- データの転送経路の指定
- HTTPでもWSでも生のソケット通信でもなんでも良いが、環境ごとに実装する必要がある
- 極端に言えばローカルのIndexedDBへローカルのクライアントから問い合わせる、などもある
バックエンドはなんでもよい。MySQL, MongoDB, In Memory、DynamoDB、etc…。 実装言語と独立しているため、サーバークライアントともに、実装は何の言語でも良い。サーバーをElixirで実装してiOSのGraphQLクライアントから呼ぶ、というようなケースも全然あるだろう。
GraphQLのスキーマ
クエリは大きく2つに分類でき、QueryとMutationがある。スキーマ上の type Query {...}
と type Mutation {...}
は特殊化されていて、それらの名前空間はクライアントの query {...}
と mutation {...}
に対応し、いわゆるトップレベルスコープのような扱いになる。それら2つは明確に区別されている。が、実際にどう副作用が起こるかは実装依存なので、queryで副作用を加えることは、一応可能である。やらないとは思うが。
雑感
目的に対して妥当な実装ではあると思うが、資料が少なく、未だ学習コストが高い。その学習コストを乗り越えれば、クラサバ間の通信仕様として、有効かもしれない。
シングルエンドポイントというのが、AWS Lambda などとも相性がよく、DynamoDBバックエンドなどとも有効な組み合わせになる。
自分はFlowtypeで書いていたが、静的型付けな言語環境では、GraphQLスキーマとその言語上の型を2つ定義する必要があり、やや面倒ではあった。ジェネレータがあるといいかもしれない。Subtypingできる言語ならややマシかも。
と思って調べてたらFlowtypeではGraphqlを直接パースするというアプローチを実装中だった。ただ、いまいち何の型をみてるのかよくわからない。Relay以外で使えるのか。https://github.com/facebook/flow/pull/2822