数ヶ月前にAWS AppSyncを使ってGraphQL APIを実装していました。 実装を進めるうえでGraphQLの詳細についていろいろ疑問や疑念が浮かんだのでいろいろ調べていました。
GraphQLのAPIをAppSyncで作ってみたのだけど、事前に期待していたほど既存のREST APIの問題を解決するものではなかったという感じ。
— 🤓k.bigwheel@転職活動中🤓 (@k_bigwheel) 2019年4月16日
ちょっとハードルを上げすぎていた感ある。
graphqlだいぶわかってきた。これREST APIの粒度よりだいぶ小さいのね。
— 🤓k.bigwheel@転職活動中🤓 (@k_bigwheel) 2019年4月18日
暴論だけどあくまでPOSTメソッドの実装方法の一例に過ぎないと。
RESTと競合するのかと思っていたけどどちらかというとRESTに内包される感じだと思った。
その中でQiitaに書かれた以下の記事が非常によかったのでそこで挙げられた疑念や欠点の指摘について2019/04時点でわかる範囲でアンサーしようと思います(主に僕の思考の整理のために 😋)。
GraphQLは90%のウェブサービス開発者にはまだ時期尚早ではないか - Qiita
免責
先に断っておくと、この記事は元記事の批判ではありません。 言及先の記事は2017/07時点での周辺ライブラリの状況や不完全な情報提供を踏まえると非常に良い記事で優れた洞察を示しています。 本記事ではそこから2年経った今の状況の反映しつつ、GraphQL APIを実装する上で僕が持った知見・意見を書きます。
元文への言及と僕の意見
LSUDsとSSKDs
一方、社内マイクロサービスにアクセスする時にはまずデータ量を減らすことに対するインセンティブが減ります。つまりGraphQLのメリットは弱まります。また、使う対象もある程度は把握できるので、最初からそこまで大量の使うかどうかも分からないデータをレスポンスに乗せない、ということもできます。
完全に同意できます。ただ、ある程度使う対象がわかっているとはいえそれらのマイクロサービスを使った新しいサービスを作るときはGraphQLで最初から書いていたほうが余計な手間が少なくて済みます。
最初からそこまで大量の使うかどうかも分からないデータをレスポンスに乗せない、ということもできます。
これをやってしまうと将来作れるサービスの可能性を狭めてしまうかもしれないわけですね。
当然ながらGraphQLでAPIを実装することはSSKDs向けの固定的なREST APIと比べてコストがかかるためROI次第ですが、僕はマイクロサービスの利点は複数の社内マイクロサービスを組み合わせて今までなかったソリューション・サービスが低コストで作れること、新アイデアの低リスク化だと思っているのでSSKDs向けのAPIでもGraphQLでそれらの未来に投資することは結構ありだと思っています。
なお、RESTに対して「サイズが小さくできる」というのが圧倒的な強みみたいに言われますが、RESTだって同じことをやっちゃいけない、ということはありません。GoogleのAPIはfieldsというパラメータを付けることで結果に含まれるレスポンスのカラムを絞る機能を提供しています。
いわゆるfieldsパラメータですね。多くのREST APIでサポートされています。(これはかなり複雑な方ですがelasticsearchの例)
これも書かれている通りだと思います。それを踏まえてRESTに対するGraphQLのメリットを挙げると、APIごとにフォーマット・実装が違うfieldsパラメータを仕様化したことと概ね1階層のfieldsしか指定できないfieldsパラメータが多い中でJSONのネストされた階層構造でもfieldsをわかりやすく統制された方法で指定できるようにしたことだと思います1。 そのメリットがGraphQLを導入することによる複雑性の増加のデメリットと比較して割に合うかはまた別の問題ですが・・・。
他の某所のチャットで話題になったところによると、バッチでまとめてもってくるのもメリットとしてあげられるけど、SSKDsならBFF: Backend for Frontend使えばいいよね、という話もありました。また、ウェブブラウザからリクエストを送信するときにも、multipart formを使うバッチリクエストをGoogleは提供していたりします。Microsoftのガイドラインでも見かけた気がしたけどソースがみあたりませんでした。
これに関してもRESTでも同じことできるよねはその通りだと思います。 RESTに対してGraphQLを使うメリットを挙げるとそのバルクリクエストの方式が公開された仕様として定まっていること、つまり実装しやすく知識の使い回しができるので利用もしやすい、という点が1つあります。
もう1つのメリットがリクエスト-レスポンスレイテンシーの低減です。 これはBFFにはできません。
こちらの例を用いて説明します。
REST APIのデメリットとしてこのページでは複数の親子関係のあるリソースの例が用いられています。 BFFを使った場合、クライアントのブラウザないしネイティブアプリからのリクエストは1つで済みますが、BFFがこの3種類のリソースへのリクエストを肩代わりすることになります。
この際REST APIがhttpベースのAPIでありネットワーク的な距離を考えるとどうしても1リクエストに付き最低でも数msのレイテンシーが発生します。 これはhttp/2のようなHTTPリクエストを束ねることができる技術でも解消できません。 なぜなら2度目、3度目のクエリでどのidのリソースを取得するかは1度目のリクエストの結果をparseしないとわからないからです。 GraphQLはそのIDをlookupしていく過程をサーバサイドで肩代わりしてくれるわけですね2。
そのためネットワークレイテンシが大きいケース、親子関係が長いケースほどGraphQLを使えば行って帰っての回数を減らすことができます。結果的に描画の高速化につながるでしょう。
クラサバ時代に戻るデメリットとメリット
ではサーバー側は、というとSQLのエンジン相当を各サーバーの実装者が書かないといけない、ということです。パーサーはあるでしょう(少なくともNode.jsには)。クエリーを見てストレージエンジンにアクセスして結果を整形して返すところは実装が必要です。
これに関しては僕も結構大変そうだなーと感じていたのですが、Resolverというデファクトスタンダード的な仕組みがあります。 これが結構便利で例えばAppSyncだとDynamoDBのResolverはこんな感じになります。
{ "version": "2017-02-28", "operation": "Query", "index": "todoid-index", "query": { "expression": "todoid = :todoid", "expressionValues": { ":todoid": { "S": "$context.source.id" } } } }
DynamoDBのクエリ文法がxxxxなので一見長いように見えますが、これSQLで言うとSELECT * from Todo where todoid = ?
に過ぎません。
fieldのフィルタリングなどは自動的にやってくれます3。
詳細に寄って、個別のfieldごとにResolverを書くこともできます。上の方のREST APIの例で言うとフォロワー全員の名前を文字列の配列として返す場合などをイメージしてください。トップレベルのリソースのデータを取ってくるクエリとそのフォロワー名を取ってくるクエリを別々のResolverにできます4。
DBへのクエリの効率化のため、JOINを使ったクエリを一度だけ発行してそれをpackingする場合はこんなに簡単には行きませんが、このResolverという考え方・仕組みはうまく複雑性の増加を緩和してGraphQL APIの実装を理解しやすく取り回しやすいものにしていると思います。
GraphQLが圧倒的な勝者になる条件が1つあります。それはGraphQLをネイティブで実装したミドルウェアやデータベースエンジンが登場し、サーバーコードを実装しなくてもGraphQLが簡単に扱えるようになることです。SQLを使うRDBのGraphQL版です。
2019/04現在、ほぼほぼそれに当たるものが登場しつつあります。 AppSyncはDynamoDBやMySQL(Aurora Serverless)に対して簡単かつ権限管理機能もついたGraphQLアクセッサとして機能していますし、今日名前を初めて知ったんですがArrangoDBというものもネイティブでGraphQLをサポートしているようです。
データベースをインターネットに晒す格好にはなりますが・・・
前述のAppSyncなどではユーザー認証とアクセスできるデータを組み合わせてそこそこ細かく制御できるので(いわゆる認可機能)、サーバレスでDBエンドポイントを公開することもありになってきたんではないでしょうか5。
まとめ
作っているサーバーのユーザーはSSKDsでしょうか?それなら今は使う必要はないと思います。利用しているウェブサービスにアクセスするためのクライアントとしての利用だけで我慢しておくほうがいいんじゃないでしょうか?あるいは簡単に使えるミドルウェアやストレージエンジンが出てくるまで待つほうがいいでしょう。
前述の通りすでに対応するストレージエンジンやDBラッパー、GraphQLのBFFサービスなどが出てきています。 Scalaでもsangriaライブラリはすでに十分なスターを集めていますし、2019/04現在十分に成熟しつつあると言っていいんじゃないでしょうか。
僕の周りではGraphQLを擁護する声は無かったけど、課題は「サーバー開発工数(複雑さがここに集中してメンテナンスとか大変かも?というのも含めて)」の一点なので、ここを論破すれば状況は変わりますので、反論エントリーお待ちしています!
ここは上でも書きましたが一からAPIサーバを実装する場合は比較的辛いままだと思います。 AppSyncのようにマネージドサービスにGraphQLの皮をかぶせたり組み合わせ可能にしたりするサービスが使えれば非常に楽かと。あとDBにGraphQLエンドポイントがある場合も使いやすいでしょうね(その場合は独自で認証・認可の仕組みが必要そうですが)。
ここから蛇足: 1週間GraphQLを調べつつ実装した感想
僕のGraphQLに対する感情はガートナー ハイプサイクルをたどって今ちょうど幻滅期を抜けたあたりです。 実際に実装せず上辺だけ調べていた時期はREST APIであやふやなあれこれの仕様(ページングやエラーハンドリング)なども合わせて定めてくれるのかと思っていましたが実際に触ってみるとfieldの絞り込みと引数、あと型定義以外はほとんど何も定められておらず結構がっかりしました。 そこを超えて単なるクエリのためのDSLとして捉えるとそこまで悪いものでもないかなと今は思っている感じです。
今後の社内マイクロサービスでGraphQLをどの程度使いたいかというとまだ微妙な温度感で、多数のリソースの種類があってしかもそれに親子関係があり、フロントなどから一度のクエリでjoinして取ってきたいという要望がある場合はGraphQLを使ってもいいと思います。 リソースの種類が少なく相互関係性が希薄なAPIなら6REST APIやgrpcのようなgraphQL以外の選択肢も十分ありです。
たまにGraphQLはネクストRESTだ、REST APIはオワコンになる的な論調で記事を書いているのを見かけますがそうは思えません。ステータスコードなどに与えるエラーの仕様がない、相変わらずhttpヘッダはGraphQLの場合も使う必要があるなどRESTのほうが優れていると感じる点も多いです7。
まとめると、GraphQLを使うときはサーバサイドでJOINできる、ネストされて構造化されたデータのfield絞り込みができるという機能を有効活用できるときにしましょう。 そのときもGraphQLをあくまでクエリのためのDSLだと捉えればREST APIからスムーズに移行したりうまく既存のREST APIと協調することができるでしょう。
-
実際GraphQLの仕様の中核はこれだと思います。AliasやVariableなんかは別になくてもなんとでもなるので。むしろあれいるのでしょうか・・・↩
-
元記事ではそれも踏まえてクラサバとあとで批判されているわけですが↩
-
まだ利用していませんがscalaのgraphqlライブラリsangriaも同様の仕組みのはず↩
-
まあ今話しているようなtype定義は実は良くないんですけどね。普通に小fieldとしてfollowerオブジェクト自体を持って、そのfolloerのnameフィールドを定義してやったほうがいいです↩
-
余談ですがこの辺Firestore, realtime databaseのようなfirebase系DBの影響が見えるような気がします↩
-
たぶん実際にGraphQLのAPIを実装した・使ったことがないかGraphQLの未来に過剰な妄想を書いているか、アオリ記事書いてPV稼ぐのが目的の人間が書いているのでしょう↩