1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Relayを「クライアント」と「Server Specification」に分けて理解するメリット

1
Posted at

はじめに

GraphQLのRelayについて調べると、次のような言葉が出てきます。

  • Relay
  • Relay Client
  • Fragment
  • Relay Compiler
  • Node interface
  • Global ID
  • Connection
  • Relay Server Specification

最初は、これらが全部まとまって見えるため、少し難しく感じます。

しかし、Relayは大きく分けると次の2つに整理できます。

Relayクライアント
  フロントエンド側でGraphQLのデータ取得を管理する仕組み

Relay Server Specification
  Relayが扱いやすいGraphQL APIにするためのバックエンド側の仕様

この記事では、GraphQL文脈のRelayについて、面接で聞かれやすい 「Relayクライアント」 と 「Relay Server Specification」 の両方に分けて整理するメリットをまとめます。


Relayを2つに分けると理解しやすい

Relayは、Reactなどのフロントエンドで使うGraphQLクライアントとして説明されることが多いです。

しかし、Relayを正しく使うには、バックエンド側もRelayが期待する形に合わせる必要があります。

そのため、次のように分けると理解しやすくなります。

フロントエンド側
  Relayクライアント

バックエンド側
  Relay Server Specification

この分け方をすると、次の点が整理しやすくなります。

  • フロントエンドで何をするのか
  • バックエンドで何を用意するのか
  • なぜその仕様が必要なのか
  • 面接でどのように説明すればよいのか

Relayクライアントとは

Relayクライアントは、フロントエンド側でGraphQLのデータ取得を管理する仕組みです。

主に次のような役割があります。

コンポーネントごとに必要なデータをFragmentで定義する
GraphQLのQueryをコンパイル時に検証する
取得したデータをキャッシュする
Mutation後に画面を更新しやすくする

たとえば、ユーザー情報を表示するコンポーネントがあるとします。

const UserCardFragment = graphql`
  fragment UserCard_user on User {
    id
    name
    avatarUrl
  }
`;

このように、コンポーネントが必要なデータをFragmentとして近くに書きます。

これにより、

このUIを表示するには、どのデータが必要なのか

がわかりやすくなります。


Relayクライアントのメリット

1. UIとデータ要求が近くなる

Relayでは、コンポーネントごとにFragmentを書きます。

たとえば、UserCard コンポーネントで name と avatarUrl が必要なら、そのコンポーネントの近くにFragmentを書きます。

fragment UserCard_user on User {
  id
  name
  avatarUrl
}

これにより、UIを変更するときに、必要なデータも一緒に確認できます。

通常のGraphQLでは、ページ単位で大きなQueryを書くことがあります。

query UserPage {
  user {
    id
    name
    avatarUrl
    email
    posts {
      id
      title
    }
  }
}

このようなQueryが大きくなると、

どのコンポーネントが
どのフィールドを使っているのか

がわかりにくくなります。

RelayではFragmentを使うことで、データ要求をコンポーネント単位に分けられます。


2. コンポーネントの再利用がしやすい

Relayでは、コンポーネントが自分に必要なデータをFragmentとして持ちます。

そのため、別の画面で同じコンポーネントを使う場合も、そのFragmentを使えば必要なデータがわかります。

たとえば、UserCard を次の2つの画面で使うとします。

ユーザー一覧画面
申請詳細画面

どちらの画面でも、UserCard_user Fragmentを読み込めば、UserCard に必要なデータを取得できます。

これにより、コンポーネントの再利用性が上がります。


3. 型安全にしやすい

Relayは、GraphQLのSchemaとQueryをもとに型を生成できます。

そのため、存在しないフィールドを取得しようとしたり、型が合わない使い方をしたりすると、開発時に気づきやすくなります。

たとえば、GraphQL Schemaに avatarUrl がないのに、Fragmentで次のように書いた場合です。

fragment UserCard_user on User {
  id
  name
  avatarUrl
}

Relay Compilerによって、ビルド時にエラーとして検出できます。

これは中規模以上の開発ではかなり大きなメリットです。

画面数やコンポーネント数が増えるほど、手作業でデータ構造を確認するのが難しくなるからです。


4. データ取得の責務が整理される

Relayを使うと、データ取得の責務が整理されます。

ページ
  大きなQueryを持つ

コンポーネント
  自分に必要なFragmentを持つ

この形にすると、フロントエンド側の設計が安定します。

たとえば、申請一覧画面があるとします。

ApplicationListPage
  ApplicationList
    ApplicationCard
      UserCard

それぞれのコンポーネントが必要なFragmentを持つことで、データ要求を分割できます。

ApplicationCard
  ApplicationCard_application Fragment

UserCard
  UserCard_user Fragment

結果として、巨大なQueryを1つのファイルに書くよりも、変更に強い構成になります。


Relay Server Specificationとは

Relay Server Specificationは、Relayクライアントが扱いやすいGraphQL APIにするためのバックエンド側の仕様です。

主に重要なのは次の3つです。

Node interface
Global ID
Connection / Edge / PageInfo

つまり、Relay対応のバックエンドでは、次のようなGraphQL APIが求められます。

IDからデータを再取得できること
一覧データをConnection形式で返せること

Relay Server Specificationのメリット

1. IDからオブジェクトを再取得できる

Relayでは、各データを Node として扱います。

interface Node {
  id: ID!
}

たとえば、申請データを表す Application 型は次のようになります。

type Application implements Node {
  id: ID!
  title: String!
  status: String!
}

さらに、root Queryに node(id: ID!) を用意します。

type Query {
  node(id: ID!): Node
}

これにより、クライアントはIDから任意のオブジェクトを再取得できます。

query {
  node(id: "QXBwbGljYXRpb246MTIz") {
    id
    ... on Application {
      title
      status
    }
  }
}

この仕組みがあると、Relayのキャッシュや再取得が安定します。


2. Global IDによってIDの衝突を防げる

通常のDBでは、テーブルごとにIDがあります。

users.id = 1
applications.id = 1
comments.id = 1

この場合、単純に id: 1 だけを見ると、それがUserなのかApplicationなのかCommentなのかわかりません。

Relayでは、型名とIDを組み合わせたGlobal IDを使います。

User:1
Application:1
Comment:1

これをbase64化して返すことが多いです。

Application:123
↓
QXBwbGljYXRpb246MTIz

これにより、GraphQL上ではIDがグローバルに一意になります。

メリットは次の通りです。

キャッシュ管理がしやすい
node(id) で再取得しやすい
型ごとのID衝突を避けられる

3. Connection形式でページネーションを標準化できる

Relayでは、一覧取得をConnection形式で返します。

type ApplicationConnection {
  edges: [ApplicationEdge!]!
  pageInfo: PageInfo!
}

type ApplicationEdge {
  cursor: String!
  node: Application!
}

type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}

クライアントは次のように取得します。

query {
  applications(first: 20, after: "cursor") {
    edges {
      cursor
      node {
        id
        title
        status
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

この形式にすると、ページネーションの扱いが標準化されます。

REST APIでよくあるページ番号方式では、次のような形になります。

/users?page=2&limit=20

一方、Relayではカーソルを使います。

applications(first: 20, after: cursor)

カーソルページネーションは、大量データやデータの追加・削除がある一覧で扱いやすいです。


なぜ両方を分けて説明するのがよいのか

Relayを説明するときに、クライアントとサーバー仕様を分けるメリットは大きいです。

1. 面接で答えやすくなる

面接では、次のように聞かれることがあります。

Relayを使ったGraphQLの実装経験はありますか?
Relay Server Specificationに準拠した実装経験はありますか?
Fragmentを活用したデータフェッチ経験はありますか?

このとき、Relayをひとまとめに説明すると、話がぼやけやすいです。

しかし、次のように分けると答えやすくなります。

フロントエンド側では、RelayクライアントでFragmentを使ってデータ要求をコンポーネント単位に分けます。

バックエンド側では、Relay Server Specificationに沿って、Node interface、Global ID、Connection形式のページネーションを実装します。

このように説明すると、フロントエンドとバックエンドの両方を理解している印象になります。


2. フロントエンドだけの話に見えなくなる

RelayはReact向けのGraphQLクライアントとして説明されることが多いです。

そのため、Relayをフロントエンドだけの技術だと思ってしまうことがあります。

しかし実際には、Relayをうまく動かすにはバックエンド側の設計も重要です。

Relayクライアント
  Fragment
  Cache
  Compiler

Relay Server Specification
  Node
  Global ID
  Connection

このように整理すると、Relayはフロントエンドだけで完結するものではなく、GraphQL API設計も含めた仕組みだと理解できます。


3. チーム開発で責務を分けやすい

Relayを導入する場合、フロントエンドとバックエンドの責務を明確にできます。

フロントエンド側の責務は次のようになります。

コンポーネントごとにFragmentを書く
必要なデータを明確にする
Relay Compilerで型を生成する
キャッシュ更新を考慮する

バックエンド側の責務は次のようになります。

Node interfaceを実装する
Global IDを返す
node(id) で再取得できるようにする
Connection形式で一覧を返す
認可チェックを入れる
N+1対策をする

このように分けることで、チーム内で「誰が何を実装するのか」が明確になります。


4. 設計の意図を説明しやすい

Relayの構成には、それぞれ理由があります。

たとえば、次のように整理できます。

Fragmentを使う理由
  UIとデータ要求を近づけるため

Global IDを使う理由
  キャッシュと再取得を安定させるため

Connectionを使う理由
  ページネーションを標準化するため

DataLoaderを使う理由
  N+1問題を防ぐため

このように、「何を使ったか」だけでなく、「なぜ使うのか」を説明できます。

面接では、単に技術名を知っているだけでなく、設計意図を説明できることが重要です。


実務での全体構成イメージ

Relayを使ったGraphQLアプリケーションの構成は、次のようになります。

React / Next.js
  ↓
Relay Client
  ↓
GraphQL Query / Fragment
  ↓
GraphQL Server
  ↓
Resolver
  ↓
Service
  ↓
Policy
  ↓
Repository
  ↓
Database

フロントエンドでは、RelayがFragmentやキャッシュを管理します。

バックエンドでは、Relay Server Specificationに沿って、ID設計やページネーションを実装します。

実務では、さらに次の観点も必要になります。

認証
認可
テナント分離
トランザクション
監査ログ
N+1対策

Relay対応のGraphQL APIは、単にGraphQL Resolverを書くだけではありません。

フロントエンドが安全にデータを扱えるようにするための、ID設計、ページネーション、認可、キャッシュ更新を見据えたAPI設計が重要になります。


面接での回答例

面接でRelayについて聞かれた場合は、次のように答えると整理しやすいです。

Relayは、フロントエンド側のRelayクライアントと、バックエンド側のRelay Server Specificationに分けて理解しています。

フロントエンド側では、コンポーネントごとにFragmentを定義し、UIとデータ要求を近づけることで、変更に強い構成にできます。また、Relay CompilerによってGraphQLのQueryを検証し、型安全に扱いやすくなります。

バックエンド側では、RelayがIDからオブジェクトを再取得できるように、Node interface、Global ID、node fieldを実装します。また、一覧取得ではConnection / Edge / PageInfo形式を使い、カーソルページネーションを実装します。

実務では、これに加えて認可チェック、テナント分離、DataLoaderによるN+1対策、Mutation時のトランザクション処理が重要になります。

短く答えるなら、次のようになります。

Relayは、フロントエンドではFragment中心にデータ取得を整理する仕組みで、バックエンドではNode、Global ID、Connection形式を用意してRelayが扱いやすいGraphQL APIにする必要があります。

まとめ

Relayは、単なるGraphQLクライアントではなく、フロントエンドとバックエンドの両方に関係する設計です。

そのため、次の2つに分けて理解すると整理しやすくなります。

Relayクライアント
  Fragment、Compiler、Cacheなど、フロントエンド側の仕組み

Relay Server Specification
  Node、Global ID、Connectionなど、バックエンド側の仕様

この分け方には、次のメリットがあります。

フロントエンドとバックエンドの責務を整理できる
面接で説明しやすい
Relayがなぜその仕様を必要とするのか理解しやすい
実務で必要な認可、N+1対策、ページネーションまで話を広げやすい

Relayを学ぶときは、まず「Relayクライアント」と「Relay Server Specification」を分けて考えると、全体像がつかみやすくなります。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?