GraphQLの練習(1) - とりあえずqueryを書きたい -

以前からやらなきゃなぁと思いつつ、使わなくてもなんとなかってた GraphQL だけど、今回 GitHub の状況をまともにレポート化しなきゃ(仕事)と一念発起し、ようやく query をアレコレ書いてみたのでその経過をメモ。

GraphQL | A query language for your API

このエントリでは実際に何らかのシステムの中に組み込んだという話ではなく、単に query を叩いてみたところまで。

とりあえずqueryを快適に書きたいよね?

いきなりだけど GraphQL そのものについての話を一切合切端折ったうえで初手で困るのはなんと言っても

schemaを活かした自動補完してくれるqueryエディタはどこにあんねん

だと思う。

GraphQL は

なんとなく :" が消えて書きやすくなったように見える Nested JSON + TypeScript の type 定義 + ちょっとした function 定義みたいな見た目

をしているが、

  • 独自の Query Language
  • どんな名前のデータにアクセスできるのかは schema 次第

なので、とにかくエディタが支援してくれないとつらすぎる。

まずこれを前提として共有しておきたい。そのうえで、ちょっとだけ待ってほしい。

まずは体験する

GraphQL query を書くに当たり、エディタの問題と同じくらいに困るもう一つ欲しいものはサンプルのデータセットだ。これについては以下のような状況になっている。

  • 世の中にはいくつか定番のお試し API がある
  • スターウォーズ API はその一つ
  • GraphQL 本家にスターウォーズ API を試す環境がある

ということで本家の公開しているお試し API がこれ。

SWAPI GraphQL AP

これは

  • Document Explorer
  • Query Editor
  • Result

の揃った GraphQL お試し画面。これによって体験できることは

  • schema に従って document を検索できる
  • schema に従って query の補完やリアルタイムバリデーションが実行される
  • 結果をすぐ横で確認できる

などで、最初の体験としてはかなりよい。schema もコンパクトなので全体感の把握が簡単なのもよい。

※ 逆に Mutation は存在しないのでその部分の練習にはならないけど、最初のお試しとしては Mutation はなくても十分だろう。

また、UI を見てみると気づくが、

  • 変数を格納しておける
  • HTTP ヘッダの追加項目も定義しておける

ので、このサンプル API では不要だが、いずれ自分が実際に利用したい API に対して query を書く際にこのような機能を持つアプリを利用できることが期待できる。

任意のAPIを自由に叩きたいのでstandaloneのエディタをください

ここまでで GraphQL の使い方がなんとなく分かったところで、

スターウォーズを調べたいんじゃないけど、この仕組みは利用してみたい

と思うわけだけど、上のお試し API の実行環境は 2022-11-21 時点で GraphiQL を利用している。

graphql/graphiql: GraphiQL & the GraphQL LSP Reference Ecosystem for building browser & IDE tools.

この GraphiQL は現在 GraphQL のエコシステムを支えるツール群としてかなり強力なものらしい。が、

どういうアプリがあってどうやってインストールするのかという案内がない

ので、はて?となってしまう。実は GraphiQL 自体はエディタ、IDE ではなく、それらを実装するためのツール群という位置付けなのだ。

そんななか、とりあえず GraphiQL 使わせろという場合は Chrome 拡張がある。

GraphiQL extension - Chrome Web Store

ただし unofficial だしいつまで続くかは微妙かもしれない。ほか、

Top 10 GraphQL clients

なんかを見ながら自分が試したものの中からインストールが簡単ですぐに使い始められたものを以下に挙げる。なお、今回は最強と名高い Apollo などサービスのアカウント作成を前提にしているものは省いた。

また個人的には

hasura/graphqurl: curl for GraphQL with autocomplete, subscriptions and GraphiQL. Also a dead-simple universal javascript GraphQL client.

これは結構面白いと思うのだが、いかんせん GraphiQL エコシステムを npm で全部揃えるのは時間が掛かるし、なぜか依存解決でいろいろ warning が出て気持ち悪いし、「補完やドキュメント参照」が最初から必要だという話なので除外した。(と言いつつ紹介はしている。)

GraphiQL Chrome拡張

GraphiQL extension - Chrome Web Store

これは上に挙げたものと同じ。

公式版ではないのでどのバージョンの GraphiQL をベースにしているのか分からない。

GraphQL Playground

graphql/graphql-playground: 🎮 GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration)

1.8.10 で確認。

  • Homebrew で install できた
  • Workspace という単位を持ち、Workspace は local も remote もある
  • query の実行時に field の自動補完が効くので field を何も書かなくてもとりあえず実行できたりする。よく分かっていない初手の段階ではかなり嬉しい機能。
  • tab で query や実行結果を保持できる
  • 機能的にはよいが、document を活用し始めるとひたすら横長になっていくのはイマイチ
  • その他、UI が「触れなくなる」不具合があり、エディタ部分以外はだいぶテスト不足な印象

V2 monaco rewrite - Needs Maintainers & Contributors! · Issue #1366 · graphql/graphql-playground

によると 今後は GraphiQL 2 が出てから、という話になっているようだが、実際には新バージョンは出ていないので、将来性は厳しそう。(誰かが fork する可能性もなくはないかもしれないけど。)

Altair GraphQL Client

Altair GraphQL Client

5.0.4 で確認。

  • 起動速度や UI の動作は小気味よいし、何より UI がきれい
  • Environment と Window がある
  • Environment は今のところ変数定義
  • Window で query を分けられるが、Window ごとに Header も分離しており、認証、認可情報の必要な API を叩く際はここが不便
    • GraphQL Playground が実現していたような機能感にはならない
  • GraphQL Playground より document の保持は弱い? 検索の反応が悪い。

※ pre-request script が定義できるのと名前付きの query を個別に呼ぶ機能があるし、query は UI ではなくちゃんと名前で分けたうえでうまいことファイルの分割などを支援しているのかも。Playground より本格的な使い方を想定しているように見える。

Chrome 拡張もある。

Altair GraphQL Client - Chrome Web Store

拡張も同じ開発元が出しているので、安心感はいちばんありそう。

学ぶとよいこと

  • ある程度以上の規模の API
  • 何パターンかの query の活用方法

があると想像しやすいのだが、以下のようなものが「とりあえずの次」に必要になってくるように思う。

queryの再利用

しばらくいろいろ情報を取得するために query を書いていると、「こういう情報を取得するためにはこの query」みたいなパターンが見えてくる。そして「全体の query としてはベツモノだけど、この情報はこれでセット」みたいな型も見えてくる。

その際は

  • named query
  • fragment, variables

を参考にするとよい。

ただし fragment は以下のように用意されている Type に対してしか定義できないので、

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}

ちゃんと小分けに type 定義の用意されている API でないと有効な選択肢にならない。

取得した情報の種類ごとの振る舞いの切り分け

実際にある程度の大きさがあり、情報の種類がいろいろある API では Interface や Union Type が利用されていて、ちょっと階層化した JSON もどきを書くだけではダメになってくる。具体的には以下のようなものだ。

{
  search(text: "an") {
    __typename
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}

どうなんだこれと思わなくはないが、field を明示しなければいけない以上、存在しない field を指定されると困るし、環境さえ整えばドキュメントの参照はそれほど難しくないので、なるほどなぁという感じ。

Schemas and Types | GraphQL

pagination

これは注意が必要。というのも GraphQL の応答に pagination 周りの情報を含めるかどうかは call 側で決められるので、REST API のように「次のページがあったら自動的に情報が突っ込まれる」ということがないため。

で、問題はどこに次のページの情報があるかというと、

Pagination | GraphQL

からの

GraphQL Cursor Connections Specification

で、

pageInfo {
  hasNextPage
  endCursour
}

という

edges と同じレベルの pageInfo オブジェクトに格納されているらしい。

もし pageInfo.hasNextPagetrue ならまだ取得できていない情報があるので、続けて同じタイプの request を投げる必要があり、その際には

first: <Int>
after: <さっき取得したendCursor>

を付けてあげるとよい。

あれこれ書いてみた感想

上のように fragment, variables, Type 分けなどで query の再利用性があるように見えるのだが、今のところ文字列の連結のような便利機能がなく、変数だけで解決しなければいけないので、

framgent と variables だけで現実と戦うの無理じゃね?

という感じがする。

例えば GitHub で pull-req / issue を検索する query に以下のように日付を指定する機能があるんだけど、

created:<YYYY-MM-DD
created:YYYY-MM-DD..YYYY-MM-DD

これは

Understanding the search syntax - GitHub Docs

にあるように単独の日付を指定する場合と range を指定する場合で明らかに記法が異なるので、variable を用意するにしても variable にセットする値を加工する誰かが必要になってしまう。そうすると GraphQL を call する言語のテンプレートなり文字列の加工なりが必要になってくるので、fragment とか variable とかそんなに重要なのか? みたいにちょっと思ってしまわなくもない。

とは言え、まず API を call してデータを取ってこれるか確認する部分では重宝するので、自分としては

  • named query
  • fragment

は MUST で考えつつ、variable はそんなに期待はしない、みたいな感じで考えている。

実際に何らかの仕組みの実装に向けて

今回は稀によく見る、

  • React アプリから GraphQL を呼ぶ
  • GraphQL サーバを作る

いずれの話でもないため、いったん GraphQL Client と query を書く際のちょっとしたコツのようなものでおしまい。

実際に何かを作りますとなったら、

  • まずはその利用するプラットフォーム上で GraphQL API を叩く仕組みを準備
  • その仕組みの開発に便利な環境向けに query を保存する方法

を考えてファイルに書き出すのでよいと思う。.gql で作ってもよいし、例えば Ruby や JavaScript から呼び出すのであればその中に埋め込まれていても、まぁそんなもんかなという気がする。埋め込んでしまった場合は query に対してそこまで強力な補完は効かないかもしれないので、そこは注意が必要。とは言え API の endpoint などの情報も言語側で持つ必要があるし、どこかで単純に API を call するクライアントアプリと実際の開発の間にはどうしても分断が起きるだろうと諦めている自分がいる。

この辺りはまだ知見が揃ってないので、そのうち言ってることが変わってしまうかもしれない。

More