この記事では、GraphQLのFragment Colocationの素晴らしさを布教します。
この記事を読むことで、以下の状態になることができるでしょう。
- Fragment Colocationとは何かを理解できる
- Fragment Colocationのメリットを知ることができる
- Fragment Colocationの使用例を見てイメージを深めることができる
この記事を読んだあなたが、
となればとても嬉しいです。
では始めます。
GraphQLのFragment Colocationとは何か?
Fragment Colocationとは、GraphQLの「Fragment」と設計の考え方の「Colocation」を組み合わせた言葉です。
それぞれを簡単に説明します。
GraphQLの「Fragment」とは何か?
GraphQLのFragmentとは、クエリで使用するデータをまとめることができる機能です。
例えば、以下のようなGraphQLのクエリがあるとします。
query { user1 { id name age } user2 { id name age } }
このような場合、id, name, age をFragmentとしてまとめて、以下のように記述することができます。
query { user1 { ...UserFragment } user2 { ...UserFragment } } fragment UserFragment on User { id name age }
このように、Fragmentを使用することで、取得するデータやクエリをより効率的に定義することができます。
Colocationとは何か?
Colocationとは、関連するモジュールやデータをなるべく近くに配置することで保守性を高めようとする設計思想のことです。
Reactの公式でも、以下のようにおすすめのファイル構成として紹介されています。
一般的には、よく一緒に変更するファイルを近くに置いておくのは良いアイディアです。この原則は、「コロケーション」と呼ばれます。
Fragment Colocationとは何か?
Fragment Colocationとは、GraphQLのFragmentを利用することで、Colocation(コンポーネントとコンポーネントに必要なデータを近くに配置すること)を実現しようとする設計の考え方のことです。
このFragment Colocationには、多くのメリットがあります。
次に、そのメリットについて解説していきます。
Fragment Colocationのメリットについて
Fragment Colocationには、主に以下のようなメリットがあります。
- コンポーネントとコンポーネントで使うデータの依存関係が明確になるため、保守・改修がしやすくなる
- コンポーネントで必要なデータに変更が生じたときに、Fragmentを修正するだけで良い(クエリは変える必要がない)ため、とても楽
- GraphQL Code Generatorと併用することで、Fragmentと紐付くコンポーネントの型を生成できる
- Fragmentとして定義することで、重複するデータを何度も取得することが無くなるため、効率がいい
色々とありますが、まとめると以下がメリットと言えるでしょう。
コンポーネントとデータの依存関係が明確になることで、コンポーネントの凝集度が高まり、保守・改修がしやすくなる
次に、よりメリットのイメージを掴むため、実例を交えて解説していきます。
Fragment Colocationの実例
最初に、Fragment colocationを適用しない場合の例を紹介します。
前提として、以下の記事に従ってバックエンドのGraphQLサーバーとフロントエンドの環境構築は終わっているものとします。
Fragment Colocationを適用しない場合の実装例
Fragment Colocationを適用しない場合、components配下は以下のようなディレクトリ構成になっています。
それぞれのファイルの中身は以下の通りです。
parts/
ページを表示する際に使用するコンポーネントのファイルです。
Header, Body, Footerでそれぞれ作成しています。
Header/index.tsx
import { Article } from "../../../generated/graphql"; type Props = { article: Article; }; export const Header = ({ article }: Props) => { const { title, author } = article; return ( <> <div>タイトル:{title}</div> <div>執筆者:{author}</div> </> ); };
Body/index.tsx
import { Article } from "../../../generated/graphql"; type Props = { article: Article; }; export const Body = ({ article }: Props) => { const { abstract, content } = article; return ( <> <div>概要:{abstract}</div> <div>本文:{content}</div> </> ); };
Footer/index.tsx
import { Article } from "../../../generated/graphql"; type Props = { article: Article; }; export const Footer = ({ article }: Props) => { const { title, author } = article; return ( <> <div>タイトル:{title}</div> <div>執筆者:{author}</div> </> ); };
templates/ArticleContent/index.tsx
上記のコンポーネントを取りまとめてページを作るコンポーネントです。
import { Article } from "../../../generated/graphql"; import { Body } from "../../parts/Body"; import { Footer } from "../../parts/Footer"; import { Header } from "../../parts/Header"; type Props = { article: Article; }; export const ArticleContent = ({ article }: Props) => { return ( <div> <Header article={article} /> <div>---------------------</div> <Body article={article} /> <div>---------------------</div> <Footer article={article} /> </div> ); };
templates/ArticleContent/index.graphql
コンポーネントで使用するデータを取得するためのGraphQLクエリを定義するファイルです。
query GetArticle($id: Int!) { article(id: $id) { abstract content title author } }
Fragment Colocationを適用しない場合の良くない点
このような実装の場合、以下のような不便な点が生じます。
- コンポーネントがどのデータを必要としているかが分かりにくい
- 各コンポーネントを確認した上で、クエリで取得するデータを決めないといけないため変更が大変
- コンポーネントで取得するデータに変更が生じた場合、クエリ(index.grqphql)を変更する必要がある
- 変更範囲(影響範囲)が広い
コンポーネントとデータの依存関係が分かりにくく、コンポーネントの凝集度も低いため、保守・改修が難しくなるのが大きなデメリットと言えるでしょう。
Fragment Colocationを適用した場合の実装例
Fragment Colocationを適用した場合、components配下は以下のようなディレクトリ構成になります。
大きな違いは、各コンポーネント(Header, Body, Footer)のファイルに、Fragmentのファイルが追加されている点です。
それぞれの実装は以下のようになります。
parts/Header
index.tsx
引数の型には、FragmentからGraphQL Code Generatorで生成したもの(HeaderArticleFragment)を指定します。
import { HeaderArticleFragment } from "../../../generated/graphql"; type Props = { fragment: HeaderArticleFragment; }; export const Header = ({ fragment }: Props) => { const { title, author } = fragment; return ( <> <div>タイトル:{title}</div> <div>執筆者:{author}</div> </> ); };
frargment.graphql
Headerで必要なデータのみをFragmentとして定義します。
fragment HeaderArticle on Article { title author }
parts/Body
index.tsx
import { BodyArticleFragment } from "../../../generated/graphql"; type Props = { fragment: BodyArticleFragment; }; export const Body = ({ fragment }: Props) => { const { abstract, content } = fragment; return ( <> <div>概要:{abstract}</div> <div>本文:{content}</div> </> ); };
frargment.graphql
fragment BodyArticle on Article { abstract content }
parts/Footer
index.tsx
import { FooterArticleFragment } from "../../../generated/graphql"; type Props = { fragment: FooterArticleFragment; }; export const Footer = ({ fragment }: Props) => { const { title, author } = fragment; return ( <> <div>タイトル:{title}</div> <div>執筆者:{author}</div> </> ); };
frargment.graphql
fragment FooterArticle on Article { title author }
templates/ArticleContent/index.tsx
import { Article } from "../../../generated/graphql"; import { Body } from "../../parts/Body"; import { Footer } from "../../parts/Footer"; import { Header } from "../../parts/Header"; type Props = { article: Article; }; export const ArticleContent = ({ article }: Props) => { return ( <div> <Header fragment={article} /> <div>---------------------</div> <Body fragment={article} /> <div>---------------------</div> <Footer fragment={article} /> </div> ); };
templates/ArticleContent/index.graphql
各コンポーネントのFragmentを取りまとめて、クエリを形成します。
query GetArticle($id: Int!) { article(id: $id) { ...HeaderArticle ...BodyArticle ...FooterArticle } }
Fragment Colocationを適用した場合の良い点
このようにFragment Colocationを適用した実装に変えた場合、以下のようなメリットが生まれます。
- コンポーネントとデータの依存関係が明確になる
- コンポーネントに必要なデータに変更が生じても、Fragmentの定義を変えるだけで良い(クエリを変更する必要がない)
- 型定義もFragmentを元に作成した型を使用しているため、変更する必要がない
- Fragmentを利用することで、同じデータを重複して取得することはなくなる
最後の「Fragmentを利用することで、同じデータを重複して取得することはなくなる」について補足します。
例えば今回の場合、HeaderとFooterでは同じデータ(titleとauthor)を指定しています。
そのため、普通に考えたら同じデータが二つ取得されそうですが、Fragmentで指定した場合は、重複は排除されます。
実際に見てみましょう。
クエリの結果を出力した場合、以下のように表示されます。
重複データはまとめられていることが分かるでしょう。
このように、重複データがまとめられるので、無駄にデータを取得することがない点もFragment Colocationのメリットと言えます。
Fragment Colocationの素晴らしさを布教したい:まとめ
今回は、GraphQLのFragment Colocationという考え方を紹介してきました。
Fragment Colocationには、以下のようなメリットがあります。
- コンポーネントとコンポーネントの依存関係が明確になるため、保守・改修がしやすくなる
- コンポーネントで必要なデータに変更が生じたときに、Fragmentを修正するだけで良い(クエリは変える必要がない)ため、とても楽
- GraphQL Code Generatorと併用することで、Fragmentと紐付くコンポーネントの型を生成できる
- Fragmentとして定義することで、重複するデータを何度も取得することは無くなるため、効率がいい
個人的には、GraphQLとReact等のコンポーネント開発をしている場合、適用しない理由がないほどの仕組みだと考えています。
この記事をきっかけにFragment Colocationを取り入れる人が一人でも増えたら嬉しいです。
最後まで読んでいただきありがとうございました。
Fragment Colocation 参考記事
- コンポーネントとGraphQLクエリの管理にFragment Colocactionを導入したら素晴らしかった件
- GraphQL の Fragment Colocation を導入したら依存関係がスッキリしてクエリもコンポーネントも書きやすくなった
- Colocating fragments