大規模 Rails で役立つテクニック: GraphQL編

# はじめに

先日、LTイベント「Qiita Night～Rails～」にて登壇させていただきました。

https://increments.connpass.com/event/297116/

そこで、Railsで大規模Webアプリケーションを開発するときに知っておきたいテクニックを Qiita の Rails を例にいくつか紹介しました。発表資料はこちらです。

<script async class="speakerdeck-embed" data-id="09d7bd2986104ccbadafb879caae3b61" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>

アーカイブ動画もあります。

<iframe width="560" height="315" src="https://www.youtube.com/embed/baynYVfPdhc" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" loading="lazy" allowfullscreen></iframe>

この記事では、LTで紹介したテクニックの１つ「GraphQL」について、どのように Rails に活用できるかを補足・紹介します。

# GraphQL とは

GraphQL は Web API の一種です。GraphQLのクエリ言語で記述したクエリに応じて、必要なデータのみ取得することができます。GraphQL について詳しく知りたい方は、公式ドキュメントや Qiita に投稿されている紹介記事などを読んでみてください。

https://graphql.org/learn/

https://qiita.com/tags/graphql

# Ruby で GraphQL

GraphQL が Rails でどのように役立つか説明する前に、Ruby で GraphQL を実装する方法を簡単に紹介します。知っている方は次の「GraphQL を Rails で活用する」に進んでも大丈夫です。

Ruby では、 `graphql` gem を使って実装することが一般的です。

https://rubygems.org/gems/graphql

公式ドキュメントも充実していて、実装時いつもお世話になっています。

https://graphql-ruby.org/

GraphQL の API を作る際に必要なものは、Resolver です。GraphQL Ruby では、 `GraphQL::Schema::Object` を継承して、APIで提供するオブジェクトを定義していきます。例として、記事を表すオブジェクトは以下のように定義します。

```rb
class Types::Article < GraphQL::Schema::Object
  field :title, String, null: false
  field :body, String, null: false
  field :tags, [Tag], null: false
end
```

APIで提供したい内容に応じて、上記のようにそれぞれオブジェクトを定義します。
また、API としてトップレベルにどんなフィールドを提供するかを定義するために、ルートオブジェクトを定義します。

```rb
class Types::Query < GraphQL::Schema::Object
  field :article, Types::Article do
    argument :uuid, string
  end
  field :articles, [Types::Article]

  def article(uuid:)
    Article.find_by(uuid: uuid)
  end

  def articles
    Article.all
  end
end
```

最期にこれをスキーマとして登録すれば完成です。

```rb
class MyAppSchema < GraphQL::Schema
  query Types::Query

  # (省略)
end
```

execute メソッドにクエリと引数を渡すことで、データを取得できます。

```rb
query_string = "
  query getArticle($articleId: ID!) {
    article(uuid: $articleId) {
      title
    }
  }"

variables = { "articleId" => "1" }

MySchema.execute(query_string, variables: variables)
# => { data: { article: { title: "記事のタイトルです！" } } }
```

あとはクエリを受け取り結果を返すエンドポイントを作れば、GraphQL のエンドポイントの完成です。

# GraphQL を Rails で活用する

LTでも紹介しましたが、大規模な Rails ではその複雑さから Fat Model や Fat Controller が起こりやすいです。この問題に対抗するには、よくある複雑になりやすいパターンを見つけ、解決方法を決めておくことが重要です。

GraphQL で解決するのは、フロントエンドに渡す様々なデータの取得です。
例として、Qiita のトップページに必要なデータを考えてみると、以下のようなデータが挙げられます。

- フォロー中のタグ
- ユーザーランキング
- アドベントカレンダーの情報
- お知らせ
- おすすめ記事

![qiita.com_ (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325392/7101a086-4033-4aae-7728-cebc003617e5.png)

Rails でフロントエンドに渡すデータを用意する時、 Controller で取得してインスタンス変数にそれぞれ代入しておき、それを View で使う、という方法が一般的ですが、必要なデータが多かったり、複雑な条件でデータを取得したい場合などは Controller だけで書こうとすると Fat Controller になってしまう場合があると思います。


そこで、GraphQL の出番です！例として、上記で定義したルートオブジェクトにQiitaのトップページに必要なフィールドを作成してみると、以下のようになります。


```rb
class Types::Query < GraphQL::Schema::Object
  field :article, Types::Article do
    argument :uuid, string
  end
  field :articles, [Types::Article]
  field :viewer, Types::User
  field :user_ranking, Types::UserRanking do
    argument :scope, string
  end
  field :recommended_articles, [Types::Article]

  def article(uuid:)
    Article.find_by(uuid: uuid)
  end

  def viewer
    context[:current_user]
  end

  def user_ranking
    ::UserRankingBuilder.new(scope: scope)
  end

  def recommended_articles
    ::RecommendedArticle.new(user: context[:current_user])
  end
end
```

そして、Controller で必要なデータをGraphQLのクエリで表現し、execute で実行します。

```rb
class HomeController < BaseController
  HOME_QUERY = <<~GRAPHQL
    query TimelineQuery($scope: String) {
      viewer {
        followingTags {
          ...FollowingTagsResult
        }
      }
      userRanking(scope: $scope) {
        ...UserRankingResult
      }
      recommendedArticles {
        ...RecommendedArticlesResult
      }
    }
    #{load_graphql('FollowingTagsResultFragment')}
    #{load_graphql('RecommendedArticlesResultFragment')}
    #{load_graphql('UserRankingResultFragment')}
  GRAPHQL

  def home
    @data = MySchema.execute(HOME_QUERY, variables: { scope: 'all' })
  end
end
```

このように、GraphQL側にそれぞれのデータをどのように取得するかを実装し、Controller ではそれを呼ぶだけにしておくことで、Controller が膨大になってしまうことを防ぐことができます。また、GraphQL側で定義したフィールドは再利用が可能です。例えばサイドバーに表示するデータなど、様々なページで同じものを表示する場合などで便利です。

## GraphQL の Mutation も役立つ

ここまで GraphQL でデータを取得する方法と、それを Rails で活用する方法を紹介してきました。GraphQL にはデータの取得(Query)以外にも、Mutation というデータの作成や更新などが可能な機能があり、それも Rails に活用できます。Controller に create や update などのアクションを追加する代わりに、Mutation で実装することで、複雑なデータの作成・更新処理で Controller が膨大になってしまうことを防げます。

# さいごに

この記事では、大規模 Rails で役立つテクニックの一つとして GraphQL を紹介しました。
他にも、 Iteratorパターンを Rails に活用する方法も紹介しているので、ぜひチェックしてみてください！

https://qiita.com/wataru86/items/523bc9fd8943e75e2def