5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Qiita株式会社Advent Calendar 2023

Day 19

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

Last updated at Posted at 2023-12-18

はじめに

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

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

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

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

GraphQL とは

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

Ruby で GraphQL

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

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

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

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

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

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

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

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

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

  # (省略)
end

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

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

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

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

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 で実行します。

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 に活用する方法も紹介しているので、ぜひチェックしてみてください!

5
1
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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?