FlowingDev

GraphQLをスタイリングする:整然としたクエリとスキーマの秘密の言語

なぜクエリからスキーマまで、一貫してフォーマットされたGraphQLコードが、読みやすさ、デバッグ、そしてチームコラボレーションに不可欠なのかを学びましょう。

ツールを試す: GraphQLフォーマッター

1行で言うと

GraphQLのフォーマットとは、クエリ、ミューテーション、スキーマに一貫したスタイルルールを適用し、ごちゃ混ぜの波括弧とフィールドの塊を、読みやすくメンテナンス性の高い傑作に変える芸術のことです。

それが解決する問題

昔は、イケてる新しいWebアプリのためにサーバーからデータを取得したいと思ったら、おそらくREST APIを使っていたでしょう。ユーザーデータが欲しければ /users/123 のようなエンドポイントに、そのユーザーの投稿が欲しければ /users/123/posts に問い合わせていました。問題は何かって?必要以上のユーザーデータを取得してしまったり(オーバーフェッチング)、逆に必要なデータをすべて取得するために何度もやり取りが必要になったり(アンダーフェッチング)することです。

そこで登場したのが、Facebookが開発したAPI用のクエリ言語、GraphQLです。これは常識を覆しました。サーバーがどんなデータを送るかを決める代わりに、クライアントが必要なものを、まさに必要なだけ、1回のリクエストで要求するのです。決まったコース料理を頼むんじゃなくて、アラカルトで注文するようなものです。

# ユーザー42の名前と、最初の3件の投稿のタイトルだけちょうだい
query GetUserNameAndPosts {
  user(id: "42") {
    name
    posts(first: 3) {
      title
    }
  }
}

これは革命でした。しかし、これにより新たな、もっと小規模な問題が生まれました。GraphQLのクエリは、ネストされた波括弧のせいで、複雑になりがちです。本当に、すごく複雑に。何のルールもなければ、ある開発者が書いたクエリは、解読不能な1行のテキストに見えるかもしれません。別の開発者は、まったく異なるインデントスタイルで同じクエリを書くかもしれません。

午前2時に問題をデバッグしようとしているときや、新しいチームメンバーがあなたのAPIの構造を理解しようとしているとき、この一貫性のなさは悪夢です。コードはコミュニケーションであり、フォーマットされていないGraphQLは、段落も句読点も、一貫したフォントすらない本を読もうとするようなものです。フォーマットは共通の文法を課すことで、コードの意図をすべての読み手にとって瞬時に明確にします。

内部の仕組み

GraphQLフォーマッターは、ただ気の利いた検索と置換をやっているわけではありません。コードの構造を理解し、一連のルールを適用し、そして美しく予測可能な形でコードをゼロから再構築するという、洗練されたプロセスなのです。

パース:テキストからツリーへ

まず、フォーマッターは生のGraphQLコードの文字列を読み込み、それが何であるかを理解しなければなりません。単に { を見つけて改行を加えればいいというものではありません。その波括弧がクエリを開いているのか、型定義なのか、それとも入力オブジェクトなのかを知る必要があります。

このプロセスはパースと呼ばれます。フォーマッターは入力をトークン化(query、user、(、id、:、"42"、) のような意味のあるチャンクに分割)し、次に**抽象構文木(AST)**を構築します。ASTは、コードの文法構造を表すツリー状のデータ構造です。

シンプルなクエリの場合:

query { user { name } }

ASTは(概念的に簡略化すると)このようになります:

- Document
  - Definition (OperationDefinition, type: query)
    - SelectionSet
      - Selection (Field)
        - name: "user"
        - SelectionSet
          - Selection (Field)
            - name: "name"

テキストはもはやただのテキストではありません。プログラムが賢く操作できる、構造化されたオブジェクトになったのです。

スタイルのルール

ASTが手に入れば、フォーマッターはツリーをたどってスタイルルールを適用できます。これらのルールはフォーマットの心臓部であり、しばしば(ほとんど無意味な)開発者の議論の的になります。一般的なルールには以下のようなものがあります:

  • インデント: ネストの各レベルで使用するスペースの数(もしあなたがタブ派なら話は別ですが)。ほぼ世界標準はスペース2つです。
  • 改行: いつ新しい行に配置するか。開き括弧 { はフィールド名と同じ行にあるべきか、それとも新しい行か?(ほとんどのフォーマッターは同じ行に置きます。)
  • スペーシング: コロンのような演算子の周りや括弧内のスペースを常に一定に保ちます。
  • フィールドのソート: 大規模なスキーマの場合、一部のフォーマッターはフィールドをアルファベット順にソートして見つけやすくすることもできます。

Prettierのようなツールは「思想が強い(opinionated)」ことで有名になりました。つまり、これらの選択をあなたに代わって行ってくれるので、あなたがそれについて議論する必要がなくなるのです。目標は「完璧な」スタイルを一つ見つけることではなく、一つのスタイルを選んで、それを徹底的に適用することです。

プリティプリント:ツリーから再びテキストへ

ルールを適用した後、フォーマッターの最後の仕事は、変更されたASTを再びテキスト文字列に戻すことです。このプロセスはプリティプリントと呼ばれます。フォーマッターはツリーを走査し、各ノード(FieldやSelectionSetなど)で、ルールに従って正しいインデントと改行を加えながら、対応するテキストを出力します。

その結果が、美しくフォーマットされたGraphQLの文字列です。

関連する概念にミニフィケーションや圧縮があります。これはプリティプリントの反対です。これもコードをASTにパースしますが、その後、すべてのオプションの空白を削除して出力します。これにより、人間には読めませんが、数バイトを節約できるため、ネットワーク経由で送信するのに最適な、1行のコンパクトな文字列が生成されます。

実話から

深夜のデバッグセッション事件

バックエンドエンジニアのジャスミンは、待機番でした。午前1時30分、アラートが鳴りました。本番環境で重要なGraphQLのミューテーションが失敗しているとのこと。唯一の手がかりは、クライアントから送信された正確なクエリを含むログエントリ――それは、ミニファイされたJavaScriptバンドルからコピー&ペーストされた、3000文字の1行のわけのわからない文字列でした。彼女はそのテキストの壁を睨みつけ、...customer{address{... の中から不正な部分を見つけようとしました。目がかすんできます。イライラして、彼女はその文字列全体をGraphQLフォーマッターに放り込みました。すると、瞬時にクエリは70行の完璧にインデントされた構造に花開きました。そして、それは47行目にありました。重要なフィールド名に address ではなく adress というタイポがあったのです。修正は些細なものでしたが、フォーマットされるまで問題を見ることさえできませんでした。

教訓: 読みやすさは、デバッグしやすさへの第一歩であり、最も重要なステップです。フォーマッターは、難解なテキストの塊を、人間が実際に解析できるものに変えてくれます。

マージされないプルリクエスト

ある小規模なチームが、GraphQLで新しいeコマースのバックエンドを構築していました。2人の開発者、リアムとオリビアが、ある機能に取り組んでいました。リアムは自分のエディタを4スペースのインデントを使うように設定していました。2スペースインデント派のオリビアは、別の設定でした。リアムがプルリクエストを提出し、オリビアがレビューしていくつかのロジックの変更を加えてコミットをプッシュしたとき、結果として生じた「diff」は赤と緑の海でした。彼らのエディタが空白を巡って争っていただけで、ほぼすべての行が変更されたとマークされていました。実際の意味のある変更は、そのノイズの中に完全に埋もれてしまいました。テックリードは、その混乱を解きほぐすのに1時間を費やすはめになりました。翌日、彼はpre-commitフックに自動GraphQLフォーマッターを追加しました。今では、すべてのコードがコミットされる前に、まったく同じ基準でフォーマットされます。

教訓: 自動フォーマットは、スタイルに関する議論をなくし、バージョン管理の履歴をクリーンに保ち、レビューを重要な点、つまりロジックに集中させます。

スパゲッティのように見えたスキーマ

あるスタートアップのGraphQLスキーマは、3年間で有機的に成長しました。型は収まりのいい場所に追加され、フィールドは順不同、コメントは散発的でした。新入社員にとって、APIのデータモデルを理解しようとすることは、古いケーブルが詰まった引き出しを解きほぐそうとするようなものでした。彼らは実験をすることにしました。schema.graphqlファイル全体をフォーマッターにかけたのです。ツールはすべてを正しくインデントしただけでなく、各型の中のすべてのフィールドをアルファベット順にソートしました。突然、idは常に最初のフィールドになりました。非推奨のフィールドは一緒にグループ化されました。全体の構造が、パッと焦点が合ったのです。それはただ綺麗になっただけではありません。今や、有用なドキュメントの一部となったのです。

教訓: よくフォーマットされたスキーマは、生きたドキュメントとして機能します。APIの構造と意図を明らかにし、誰にとってもより親しみやすいものになります。

よくある間違いと落とし穴

  • スタイルに関する論争。 最大の落とし穴は、タブかスペースか、波括弧はどこに置くべきかといった議論に時間を浪費することです。フォーマットの価値は一貫性にあります。Prettierのような人気のある、思想の強いツールを選び、それを使うことに同意し、次に進みましょう。
  • コミット前のフォーマット忘れ。 フォーマットが手動プロセスだと、人々は忘れてしまいます。これは、あなたが避けようとしていた汚いdiffにつながります。pre-commitフック(Huskyやlint-stagedのようなツールを使用)にフォーマットを統合して、自動的かつ楽に行えるようにしましょう。
  • フォーマットとリンティングの混同。 フォーマッターはコードの見た目を一貫させます。リンター(eslint-plugin-graphqlなど)は、非推奨フィールドの使用や非効率的なクエリの記述など、潜在的なバグや悪い慣行がないかコードをチェックします。両方が必要です。フォーマッターがキッチンを掃除するなら、リンターはコンロの火を消し忘れていないかチェックするようなものです。
  • 生成されたコードのフォーマット。 一部のワークフローでは、他のソース(データベーススキーマや別のプログラミング言語など)からGraphQLスキーマファイルやクエリを生成します。出力をフォーマットしても、次回コードが生成されるときに変更が上書きされるため、時間の無駄になることが多いです。代わりにソースをフォーマットしましょう。
  • 本番環境で整形済みクエリを送信する。 開発中は美しいインデントが素晴らしいですが、ネットワーク上では無駄なバイトです。ビルドプロセスで、クライアントアプリケーションからサーバーに送信される前にGraphQLクエリをミニファイすべきです。

なぜ注目すべきか

プロジェクトに2人以上の人が関わるようになった瞬間、あるいはクエリが単一のネストされたフィールドよりも複雑になった瞬間に、GraphQLのフォーマットについて考え始めるべきです。

これは、プロフェッショナルなソフトウェア開発の基本的なツールであり、たまたまGraphQLに完璧に適用できるものです。「綺麗」にするためだけにやっているのではありません。それは、以下のためです:

  • 明確さ (Clarity): コードを読みやすく、理解しやすくするため。
  • 保守性 (Maintainability): コードの変更やデバッグを容易にするため。
  • 協調性 (Collaboration): スタイルに関する選択を自動化することで、チームメンバー間の摩擦を減らすため。

もしあなたがログファイルの中のミニファイされたGraphQLクエリを睨みつけていたり、チームメイトとインデントについて議論していたりするなら、それはあなたの人生に自動フォーマッターが必要だというサインです。

さらに深く

理論はOK。さあ手を動かそう — 100%ブラウザ内で。

ツールを試す: GraphQLフォーマッター