「SP版サロンボード」へGraphQL導入と考察
加納 英樹
「SP版サロンボード」へのGraphQL導入と考察
目次
- はじめに
- 案件概要
- 導入背景
- BFF GraphQLの導入
- GraphQLライブラリの比較
- 考察
- さいごに
はじめに
リクルートの美容領域でフロントエンドエンジニアをやっている、加納英樹と申します。 今年で新卒4年目になります。この記事では、自分が2022年4月から1年ほど関わっている スマホ版(以下、SP版)サロンボードリプレイス案件の裏側について話したいと思います。 今回は、GraphQLを導入した理由と、今後横展開する際に気をつけるべきポイントを述べたいと思います。 次回、もう少し技術に寄った話を書こうと思います。
案件概要
HOT PEPPER Beautyのサロン向け予約管理システムとして、サロンボード というものがあります。サロンボードはPC版とSP版がありますが、今回はSP版をリプレイスすることにしました。 実際のアプリケーションの様子(下図)を見ていただくとお察しいただけるかもしれませんが、 数年間、大きな改修が入ることなく、必要最低限の改修のみが行われるに留まっており、 使いづらいといった声や、システム老朽化など多岐にわたる課題がありました。 そのため、本腰を入れて、改修することにしました。 なお、サロンボードはありがたいことに多くのお客様にお使いいただいており、 予約管理という特性上、非常に高い機密性、完全性、可用性が求められるアプリケーションであると言えます。
導入背景
BFF GraphQLの導入
まず、バックエンドチームとフロントエンドチームに分けることはほぼ必至でした。 チームを分けないほうが優れているケースもありますが、以下の理由により、今回は明確に分割する方式を選択しました。
- メンバーの得意技術スタック・業務経験が2分(Java系 か HTML/JS系)している。
- 開発者だけで10人を超えている。
- SP版でスマホネイティブ並の操作感が要求され、SPAはほぼ必須。
その場合、バックエンドは極力画面のことを考えなくて済むように、各ドメインごとにAPIを切るように設計するのがよく、 BFFを立てて画面用に加工するのが筋の良い設計だと考えています。 そういった前提があり、FE-BFF間のプロトコルをREST API にするかGraphQLにするかどうかの検討が必要でした。
GraphQLのメリットは、クエリが柔軟に書ける点や、先進的なUIが容易に提供できる点、 Fragment Colocationができる点などがあります。 ただし、前者の2点は要件や開発フローに大きく依存することから、 技術選定時点ではメリットになるか正直微妙なところでした。 一方、Fragment Colocationについては、確実にメリットであると考えたため、 GraphQLを導入することにしました。 以下にFragment Colocationについての説明を記載します。
従来のREST APIでやる場合、Page相当のコンポーネントでfetchを行い、 fetchした結果を元に各子コンポーネントにバケツリレーで渡す必要がありました。 例をコードで示すと以下のようになります。
1
2
3
4
5
6
7
8
9
10
// propsのバケツリレーの例
const Page = () => {
const { data } = useQuery() // useQueryががhogeを知らないといけない
return <ChildComponent hoge={data.hoge} />
}
const ChildComponent = (props) => {
const { hoge } = props
return <div>{data.hoge}</div>
}
上記のコードからわかるように、親コンポーネントが子コンポーネントのプロパティの具体の中身(この例だとhoge
)を知っている必要があり、hoge
がhoge2
に変わったら全て置換しなければならないといった保守上の問題があります。
ところがGraphQLの場合、Colocationという機能が標準で搭載されており、 親コンポーネントは子コンポーネントにどんなプロパティを渡すかを知らずにすみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// GraphQLの例 (Relayの場合)
const query = graphql`
query pageQuery {
...ChildComponent_Query # hogeを知る必要がない
}
`
const Page = () => {
const data = usePreloadQuery()
return <ChildComponent fragmentRef={data} /> // "hoge"を渡していない
}
const childFragment = graphql`
fragment ChildComponent_Query {
hoge
}
`
const ChildComponent = (props) => {
const { data } = useFragment(childFragment, props.fragmentRef)
return <div>{data.hoge}</div>
}
これであれば親は子の中身を知らなくて済むので、hoge
がhoge2
に変わったところで親への影響はありません。
枯れていない技術採用による未知の事象や、要員調達リスクの方が大きいのでは?という声もありましたが、弊社にはスタディサプリでの導入実績があること、かなり意欲的なフロントエンドメンバーが集められたこと、社内横断のフロントエンド専門チームのサポートを得ることができるなどの理由から、GraphQLを採用するに踏み切りました。
GraphQLライブラリの比較
GraphQLの導入が決まったところで、ライブラリの比較が必要でした。 主要なライブラリの比較結果は以下の通りです。なお、Next.jsとReact.jsを使用する前提とさせてください。
クライアント側
結論から述べると、クライアント側はRelayを選択しました。 対抗Apollo Client、次点でUrqlといった選択肢が挙げられます。 Relayを選んだ理由は以下の通りです。
- 書き方に関するルールが厳しいこと
- これは大規模案件であることや、今後運用が続いていくことを考慮すると非常に大きなメリットである。lintルールやdocumentで縛るのには限界があり、コンパイラレベルでエラーを出すことができるのは良い。
- Apollo ClientやUrqlは柔軟さをウリとしているため、上記の考え方と合わない可能性が高い。
- 開発元がReactと同じであること
- Suspenseをはじめ、Reactの新機能への対応が早い。
- Reactの考え方に沿っており、Reactのバージョンアップ時にマイグレーションコストが低い可能性がある。
- Persisted Queryに対応していること
- 要求「PublicなGraphQL Endpointではないので、allowlist上のクエリしか許可したくない」の実現のためには、Persisted Queryが必要になるが、デフォルトで搭載しているのはRelayのみ。
Relayは、ドキュメントの少なさや発展途上感が懸念されましたが、 弊社のフロントエンド専門チームが既に開発研究を進めており、 案件実装を進める上で大きな問題となることはないと考えました。
サーバ側
サーバ側はYogaを選択しました。比較対象のApollo Serverと比べると、 以下の点で優位にあると考えました。 なお、どちらも中身のエンジンはgraphql-jsであり、Next.js上で動かすだけであれば大きな違いはないと思われます。 拡張性の良さで選びました。
- Persisted Queryの存在
- クライアント側で述べたように、Apollo ServerのPersisted Queryとは別物。
- Yoga Serverには
usePersistedQuery
プラグインがある。
- Pluginの充実度
- Envelop Pluginが使用できるようになっている。
- Apollo ServerのBuild-in Pluginよりも数が多い。
考察
技術観点
あらかじめ先人たちの記事に目は通したものの、プロダクション運用において発覚したいくつかの壁がありました。 特にRelayに関しては実例も少なく、非常に対応が困難でした。どのように解決したかについても合わせて記載しておきます。
- Relayを用いたRender-as-you-fetchとSSRの両立
getInitialProps
を使うことで実現できます。- そこそこ難解なようで、新規参画者が首を捻っている印象でした。
- 動作を追うときに、サーバorブラウザどちらで動いているのか常に考えなければならない。
- コードがブラウザにも露出する点に注意しないといけない。
- 詳細や解決方法は、解説記事を参考にしてください。
- RelayにおけるOffset Based Paginationの実現方法
- RelayはCursor Based Paginationが推奨です。
- ただし、UI要件としてOffset Based Paginationがある場合は、何かしらの方法で実現しないといけません。
- 各ページのCursorのリストを、
Connection
に持たせることで解決しました。 - 後日詳細な解説記事を出したいと思っています。
- Resolverの型定義問題
- graphql-codegenが生成する型はOptionalで、各フィールドのリゾルバーを実装しなくてもコンパイルが通ってしまう。
- リゾルバーの実装漏れに実行するまで気づけない。
- Optionalを外して全てのフィールドをRequiredにすることで型レベルでのエラーを検知できるようにしました。
- 長くなってしまうので、こちらも別記事にて解説を予定しています。
今回GraphQLを使うにあたり以下のサイトを参考にさせていただきました。
- https://engineering.mercari.com/blog/entry/20220303-concerns-with-using-graphql/
- https://moneyforward-dev.jp/entry/2023/02/20/100000
- https://techblog.yahoo.co.jp/entry/2020121530052952/
非技術観点
既に動いている大きなプロダクトのリプレイスでGraphQLを導入する事例は多くないと思うので、そのような観点での考察を述べたいと思います。 結論からいうと、GraphQLを最大限活用するためには、事業や組織を巻き込み、協力が不可欠であることを感じました。 逆に、協力が得られない場合は採用すべきではないと思います。
知見としては以下の通りです。
- GraphQLのクエリの柔軟性を最大限生かすべき。
- 柔軟性 = あとから容易にインターフェースを変更できることだと考えています。
- 画面の変更や表示要件の変更が容易になるので、あとからの改善やエンハンスがしやすくなります。
- 先に長い時間をかけて検討し作るのではなく、まずは出してみてユーザの反応を得るという選択肢がとりやすくなります。
- 要件への介入をすべき。
- Partial Rendering、Subscription、カーソルベースページネーションなどにより、先進的なUXが簡単に提供できます。
- これらは要件検討段階では考慮されていないこともあるので、積極的に提案してくことが重要です。
- 最新のReactやNext.jsの知識が前提にあってこそのGraphQL。
- 必要となる関連知識が多く、身につける必要がある。
- 結構個人のスキルやスタンスに依存してしまったので、技術継承のやり方も工夫の余地があると思いました。
技術的に強いメンバーが集められたことが何よりの成功要因だったと考えます。
さいごに
SP版サロンボードでは開発効率向上のため、今回導入したGraphQL以外にも、モダンなツールやライブラリを導入しています。
モダンな環境で働きたいフロントエンドエンジニアを募集しています!