GraphQLとそのクライアントライブラリ「Apollo Client」の話です
GraphQLで開発を行っているチームに向けた社内勉強会用の資料です。
コードはボイラープレート化されていますが、GraphQLのメリットや全体像について理解を促進する目的で作成しました。
Topic 💁
・GraphQLの特徴
・Apollo Client
・GraphQL Code Generator
・開発の流れ
GraphQLの特徴
GraphQLの特徴 - Schema
APIとクライアント間のインターフェースとして利用される
type Post {
id: String!
created_at: DateTime!
}
GraphQLの特徴 - Schema
クライアントからは
・request / responseの型情報
・データ取得関数の自動生成
として利用される。
GraphQLの特徴 - Schema
・補完が効いて開発効率が上がる ✅
・型安全になり修正・リファクタ時の負荷が軽減される ✅
e.g. API変更の影響をざっくり知りたい
$ npm run lint:ts
GraphQLの特徴 - RESTとの比較
・クライアントがレスポンスを組み立てる
・基本的にリソース=リゾルバ (エンドポイント単位でのサブリソースの管理が不要)
e.g. 記事ページのAPIにコメントを生やしたが、他ページでもコメントを取りたくなった
GraphQLの特徴 - RESTとの比較
・クライアントファーストな仕組みと言える
・クライアントに複雑性が移動してきた背景を考えるとGraphQLの登場は自然な文脈
・学習・環境構築コストが高く、パフォーマンス出しにくいため使い所は見極めたい
Apollo Client
Apollo Client
・クライアント側のGraphQLライブラリ
・旧facebook社が開発したRelayと2大巨頭
・後発ながら機能が豊富で利用者が多く、Relayに比べて制約が弱め
Apollo Client - 状態管理
コンポーネントを跨いで利用したい状態をグローバルステートと呼び、その管理方法が「状態管理」と呼ばれている
状態管理で扱うデータの種類は大きく2つ
・API経由データ
・それ以外データ (共通モーダルの表示など)
Apollo Client - 状態管理
Reduxなどを利用してコンポーネントを跨いで利用したいグローバルステートを管理するのがセオリー化している
下記のような課題がある (と感じている)
・ボイラープレートが多くて大変
・コード量が多くなると職人が必要
Apollo Client - 状態管理
SWR, useQueryといったキャッシュ機構を持つライブラリが流行ってきた
前述の「API経由、それ以外データ」を識別子単位でメモリキャッシュしてくれる機能を持っている
Apollo Client - 状態管理
apolloもキャッシュ機構を持っていて、更にオブジェクトごとに正規化してくれる機能がついている
// __typename x idを識別子としてユニーク性を担保
{ __typename: 'Post', id: 'uuid-1', name: 'something' },
{ __typename: 'Post', id: 'uuid-2', name: 'anything' }
Apollo Client - 状態管理
下記のような挙動が可能
・ミューテーションで記事を更新する
・記事一覧のキャッシュが更新される
・記事一覧の表示が更新される
-> API経由データの更新時にグローバルステートの更新が不要になる 👏
Apollo Client - 状態管理
それ以外データは「Reactive variables」という機能を利用して、従来通り自分で変更します。
Apollo Client - 状態管理
// store.js
const flagVar = makeVar(false);
// component.jsx
const Component = () => {
const flag = useReactiveVar(flagVar); // reactiveに変更を検知
return <div onClick={() => flagVar(true)}>{flag}</div>;
}
GraphQL Code Generator
GraphQL Code Generator
下記を自動生成してくれるツール
・APIのレスポンス型
・データ取得のhooks
GraphQL Code Generator
このとき自動生成されるコードは下記を参照している
・Schema
・オペレーションコード (クライアントからのリクエスト)
GraphQL Code Generator - query(参照)
Schema
// ルート型
type Query {
post: Post // フィールド(RESTのendpointに相当)
}
// オブジェクト型
type Post { id: String! title: String }
GraphQL Code Generator - query(参照)
オペレーションコード
query PostPage { // オペレーションタイプ オペレーションネーム
post { // フィールド
id
title
}
}
GraphQL Code Generator - query(参照)
生成されるコード (抜粋)
type PostPageQuery = { // レスポンス型
__typename: 'Post';
id: string;
title: string | null | undefined;
};
const usePostPageQuery = useQuery(...); // hooks
GraphQL Code Generator - query(参照)
hooksの使い方
const Component = () => {
const { data, loading, error } = usePostPageQuery();
if (error) return throw error; // エラー
if (loading) return <div>Loading</div>; // ローディング中
return <div>{`${data.post.id}: ${data.post?.title}`}</div>;
};
GraphQL Code Generator - mutation(更新)
Schema
type Mutation {
update_post(input: UpdatePostInput!): UpdatePostMutation!
}
type UpdatePostInput { id: String! title: String! } // パラメータ
type UpdatePostMutation { post: Post } // レスポンス
GraphQL Code Generator - mutation(更新)
オペレーションコード
mutation PostPageUpdate($input: UpdatePostInput!) {
update_post(input: $input) {
id // ここからレスポンス
title
}
}
GraphQL Code Generator - mutation(更新)
生成されるコード (抜粋)
type PostPageUpdateMutation = { // レスポンス型
__typename: 'PostPageUpdateMutation';
post: Post;
};
const usePostPageUpdateMutatiion = useMutation(...); // hooks
GraphQL Code Generator - mutation(更新)
hooksの使い方
const Component = () => {
const [mutate] = usePostPageUpdateMutatiion(); // 関数取得
const handleUpdate = async () => {
const { error } = await mutate();
if (error) return throw error; // エラー
};
return <button onClick={handleUpdate}></button>;
};
開発の流れ
開発の流れ
GraphQLでの開発方法は主に2種類
・Code First
型・リゾルバーからスキーマ生成 (我々はこちら)
・Schema First
スキーマからコード生成 (gqlgenなど)
開発の流れ
[BE] テーブル設計
[ALL] ER図とデザイン見てスキーマ相談
[BE] リソースの型とリゾルバー(ロジック)書く
[BE] スキーマ自動生成
[FE] オペレーション書く
[FE] レスポンス型・hooks自動生成
開発の流れ - 課題
このチームはBE/FEが別れており、コードファーストのためスキーマを相談するタイミングが見失われがちです。
デザイン・ER図ができたタイミングで、スキーマを相談するタイミングがあるとスムーズに実装を進められます<(__*)>
最後に
kibelaにドキュメントを作成しています 💁
よく使う用語やライブラリのAPIを記載していますので、困ったときにご覧ください。
・GraphQLの用語集
・[FE] ApolloClientの使い方