GraphQL備忘録
本稿を記す動機
そもそもの発端としては、node.js で作成した Web API があり、それが参照する既存テーブルがCRUDを意識していないものだから、date とか time とかいうカラムを持ち、それを結合したフィールドで範囲指定したデータを抽出するような機能を追加したいと思ったときに、適切と思えるエンドポイントが思いつかないというところから始まった。そもそも Web API の経験が少ないので、厚顔無恥なURLでエイヤっとやるような勢いを持っていなかったわけである。
どのようなやり方がふさわしいのかと調べているうちに GraphQLというワードを拾い、興味を覚えて、2019/07頃から試行を始め、結果として、エンドポイントに悩むようなAPIについて非常に優位性のある技術ではないかという感想を得た。
ただし、テーブルが二つしかなく、かつ結合条件での問い合わせなどない環境であるので、GraphQLを語るときに(おそらくは rails を念頭に置いて)も抜きにはできなくなったN+1問題とかには触れていない。
これと前後してRust に興味を持ち、チュートリアル後の課題を探したとき、まずは既存の自作プログラムと似たようなものを作ろうとして赤外線リモコン送信を試みたが挫折し、いじけていたところに、よせばいいのにGraphQLを思いついてしまった。
モジュールのバージョンが1.0未満だったり、参考にした記事がちょっと古いだけでもう役目を終えていたり、なかなかこれといった情報に巡り合えず、これも一時断念。逃避的思考からか、GO言語でもできるのかなとふと思い立ち寄り道してしまったが、Rustと同様のアレコレに出くわして思うようにはかどらなかった。2019/08~09は、挑戦がことごとく失敗する魔の期間であった。
躓きすぎて投げだしそうになったが、あきらめきれずもう一度 Rust で試したところ、diesel の使い方がわかって進捗を得た。railsのようなフレームワークの使用経験はプライベートにとどまり、あまり熱心に学んでもおらず、オマジナイとして使用してきたのだが、少しやる気を出して理解してみたカンジ。ひとつわかってみればというところで、gqlgen の使用方法の理解にもつながり、GO言語での実装も果たせた。
以上の取り組みにあたって初期に設定した命題と、結果的に比較することになってしまった実体験から得られた差異と所感について以下に述べる。
使用した言語とモジュール、命題への回答
1. node.js
- apollo-server
- sequelize
- graphql
2. GO
- gqlgen
- gorm
命題への回答
gqlgen は、テーブルは既存が前提で、テーブル定義に対する構造体(schema.graphql)さえかければ、gqlgen が必要な定義(generated.go)を自動生成するので、あとは不足を補ってくださいというスタイルだ。テーブル定義を変更した後、再度 gqlgen することによって先の定義を再生成するので、変更箇所もわかりやすいかもしれない。
- timestamp や datetime の扱いにちょっとした細工が必要。
- クエリの引数でオプショナルな指定ができない?ため、SQLのWHERE句に相当する文字列を与えるようにするか、ちょっとアレなインターフェイスにするしかないっぽい。
- schema.graphql はそもそも手作りなので、既存テーブルに際しても特に躓きはない。(migration しないと特定のファイルが生成されない、というようなことがない)
- gqlgen によって models_gen.go を自動生成した後、実テーブルの対応を手作りせねばならない。gorm に親しんでいれば問題はないと思われるが、カラムの型によっては悩みどころになるかもしれない。
- gqlgen によって生成された generated.go にインターフェイスが定義されるので、それを resolver.go に手作りしていくという流れが理解できるまで、誰が何をしてどうせねばならないのかわからなかった。
3. Rust
- diesel
- juniper
- serde
5. Python (2019/10/31 追記)
- uwsgi
- graphene
- Flask-GraphQL
- SQLAlchemy
- graphene_sqlalchemy
まとめ
今回設定した命題については、node.jsによる実装が自由度が高いと感じられる。
とはいえ、GraphQL における mutation のスタンダードが diesel や gqlgen のようなものであるならば、少なくとも apollo-server では現時点でそれは実現できないようである。しかしながら、diesel や gqlgen はまだまだこれからという印象があり、また、どちらにも善し悪しがあり選び難い。まだまだ途上の技術である、というところか。
余談としては、prisma がよさげに思えたのだが、docker 必須であり、手元にある機器(windows10、raspbian)では実現できず、期待度は高いが試行環境の構築から躓いている。
2019/10/08 追記:
n+1問題について未検証でなんともいえないが、実装のスピーディさでは rails がかなり良い。言語を問わずGraphQLの実装検討を要望されたら、筆頭候補に挙げてもいいかもしれないと思える。
適用対象となるであろうラズパイでは rails は重すぎるという印象だったが、やってみたらそうでもなかった。ただし、buster だとrubyおよびrailsのインストールにやや難がある。
2019/10/31 追記:
node.jsとPythonによる実装は自由度や難易度的に同程度という印象。Pythonのgrapheneは、Pythonを書いているというより、ライブラリの作法に従っているという感があり、勘所を要する印象。その点については、apollo-server を使用した node.js の方が学習コストが低いかもしれない。あるいは、grapheneの方が良いサンプルを見つけにくいということかもしれない。
node.js、GO、Rust、Rails、Pythonと試してきたが、更新クエリの書き方として query variableを使用するもの(GO:gqlgen、Rust:diesel、Rails:graphiql-rails)と使用しないもの(node.js: apollo-server、Python:graphene)の比が3:2であり、どちらが主流なのかよくわからない。クエリとデータを明確に分けられるという意味で、バッチ的な作成更新処理を行うなら前者がよさそうな気もする。
2019/11/02 追記:
ここまでで、オススメ順は以下の通り。
4 >= 1 = 5 > 6 > 3 > 2
(1. node.js、2. GO、3. Rust、4. Rails、5. Python graphene、6. Python graphene + django)
ただし、Rails は日付の扱いがフレームワーク依存らしいので、その辺を仕様で吸収できる場合に限る(DBには常にUTCで保存、表示時にJSTにする、とか)。
2019/11/12 追記:
Paginationについて、rails、python、node.jsで検討し、以下のように変更。
4 >= 5 > 1 > 6 > 3 > 2
理由としては、node.js の Relay Cursor Pagination は、react とか既存のフレームワークを使えという指標なのか、GraphQL単体での実装方法が見つからなかったためである(既存のREST APIをデータソースにする方法、first・skipの形式のものは見つかった)。