Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

GraphQLを導入してDX向上(フロントエンド編)

こんにちは。Eight事業部の青山です。現在は社内の名刺を一括管理できるEightのサービス"企業向けプレミアム"で主にフロントエンドを担当しています。

今回は以前投稿されたGraphQL導入記事のフロントエンド側について紹介したいと思います。

REST APIが主流のプロジェクトの中でGraphQLを導入してみた話(サーバーサイド編) - Sansan Builders Blog

導入の経緯やサーバー側については上記記事を参照ください。

Eight本体はReact+Reduxで構成されており、今回GraphQLを利用するにあたりデータ管理をどうするかサーバーエンジニアも交えて議論しました。結果として、Apollo Clientを使ってデータの管理を行うことにしました。

github.com

Apollo ClientはApolloというGraphQLを利用するためのツール群の一部で、GraphQLサーバーに対するクライアントライブラリを提供します。同様の候補としてFacebookのRelayもありますが、データストアとしても活用しやすい、ドキュメントが充実している、連携するツール群が豊富といった点から、Apolloを採用しています。 豊富なツールの中でも、graphql-code-generatorとApollo Serverによって開発者体験(DX)が大幅に向上しているのでそれらを紹介します。

graphql-code-generatorによるコードの自動生成

Apollo Clientは様々な言語に対応していますが、その中で@apollo/react-hooksというライブラリでReact hooks*1としての利用が可能となります。

React hooksが提供されることで、画面の構成についてはReactの関数コンポーネント(Function Component)*2でシンプルに作り、提供されるhooksにデータとの結合面を任せるという棲み分けがしやすくなります。 そのうえでcontainerという関数コンポーネントとhooksを結びつけるためのコンポーネント層を準備して整理しています。*3

そしてこのhooksは、graphql-code-generatorというツールによって自動生成が可能です。

graphql-code-generator.com

今回のサービスでは先の記事の中で言及されているように、最初にRubyでSchema定義を記述します。それを踏まえた流れは以下のようになります。

  1. RubyでSchema定義
  2. rakeコマンドで.graphqlファイルを生成
  3. チーム全体でのレビュー
  4. フロントエンドで.graphqlファイルを取り込み
  5. npm scriptsに登録したコマンドでhooksコードを生成
  6. Reactコンポーネントに適用

graphql-code-generatorではApollo Client用のpluginが準備されており、@apollo/react-hooksを利用するためのコードがTypeScriptで出力されます。TypeScriptで書かれているので、GraphQLでの型定義を引き継ぐことができます。

そのため、IDEなどによる静的チェックやコード補完が可能となり、これまでのJavaScriptによる開発に比べてテストするべき箇所が限定できるようになります。安心感もありますし、開発効率もあがります。

例えば、useMembersQueryという自動生成されたhooksを利用してデータを取得する際のコードは以下のようになります*4が、データの属性がサジェストされるため、都度仕様を確認する必要がなかったり、タイプミスによる不具合を減らすことにつながります。

f:id:mt-blue81:20200718153729p:plain

またひと工夫として、GitHub Actionsを利用した.graphqlファイルの監視もしています。更新時にはPRに取り込み漏れを避けるためのコメントが付くようになっていますが、開発チームがまだ小さいため、現状効果が薄いのが悲しいところです。

Apollo Serverによるモックサーバー

ApolloにはApollo ServerというGraphQLサーバーを提供するツールも準備されています。

github.com

今回はこのApollo Serverをフロントエンドでのローカル開発用モックサーバーとして活用しました。

基本的には先の.graphqlファイルをApollo Serverに渡してやるだけで準備完了で、シンプルなモックサーバーが起動できます。 クライアント側もwebpack-dev-serverで提供されるため、簡単に開発環境が準備できます。

実際に開発用リポジトリのREADME.mdでは以下のように手順化されており、すぐに開発がスタートできるようになっています。

f:id:mt-blue81:20200718154025p:plain

モックデータのカスタマイズも可能で、今回はfakerというライブラリを使ってメールアドレスや人物名といったデータを実際のものに近づけて開発を進めました。

レビュー済みの定義ファイルさえあれば、完全に独立して開発ができますし、GraphQLベースでやり取りされるため、サーバーと組み合わせたときにも不整合などが起きにくく、安心して開発を進められました。

その他の工夫

落ち穂拾い的ですが、上記のDX向上以外にもいくつかApollo(GraphQL)を利用するにあたって気にした事項を簡単に取り上げます。

無限スクロール

データを一覧するような画面では基本的にページネーションUIではなく無限スクロール方式を取り入れました。画面としてはreact-windowを利用していますが、流し込むためのデータをどのようにやり取りするかという問題がありました。

今回はその解決案として、Relayが提唱しているCusor Connectionsを採用しました。cusrorというデータの位置を示す概念をベースに、追加データ読み込みのための情報を提供する、というものです。詳細はチュートリアルにも紹介されているので参照ください。

www.apollographql.com

データキャッシュ

Apollo Clientは独自のキャッシュ機構を持っており、キャッシュポリシーを渡すことでよしなにキャッシュ生成、利用をしてくれます。

idをベースにして正規化されているため、シンプルな更新処理であれば、なにも考えずにリソースを返すだけでキャッシュが更新され、画面にまで反映が及びます。

とはいえ、データの挿入や削除といった場面ではそうもいきません。 こういったときのためにmutation用のhooksにはupdateというoptionがあります。更新対象のキャッシュを取得し、書き換えるという操作が可能です。(対象キャッシュがないと例外となるという難しさもありますが)

ちなみにキャッシュ状態の確認などにはApollo Client Devtoolsを利用すると便利です。

エラー制御

先の記事でも取り上げられていましたが、エラーの取り扱いについてはサーバー側と意識を統一して、以下のように進めました。

  • 共通となるエラー
    • ネットワークや認証など、API特有ではないエラー
    • これらはApollo Clientインスタンスが制御する(GraphQLエラー、Networkエラーとして取り扱われる)
  • API固有のエラー
    • バリデーションエラーなどAPI固有のエラー
    • レスポンスにerrorsというフィールドを設けて、固有のエラー定義にもとづく情報を返却する
    • hooks利用側で制御するための実装を組み込む

まとめ

今回はGraphQLというよりはApolloの紹介となってしまいました。

Apollo自体はローカルステートの管理ができるなど、まだまだできることがたくさんあります。プロダクトとしての初めての利用でしたが、手探りながらも非常に良い開発体験が構築できたと感じています。

また、紹介した内容以外でも、Create React Appを利用した開発スタートダッシュや、storybookによるsnapshotテストの導入、eslint & stylelint & prettierによるコードレビューコストの削減など、ゼロスタートだったために様々な試みができましたが長くなってしまうためこのあたりで。


buildersbox.corp-sansan.com buildersbox.corp-sansan.com buildersbox.corp-sansan.com

*1:React16.8で導入されたReact関数コンポーネントに状態や副作用を与えるための機能。ロジックをコンポーネントから切り離すことができる。

*2:JavaScriptのクラスを使わず、関数のみで表現されるReactコンポーネント。

*3:Redux利用などでもデータと結合する部分を同様の形式で取り扱うことが多いと思います。

*4:実際には追加読み込み処理などを実装したラップをしています。

© Sansan, Inc.