はじめに
先日、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 のトップページに必要なデータを考えてみると、以下のようなデータが挙げられます。
- フォロー中のタグ
- ユーザーランキング
- アドベントカレンダーの情報
- お知らせ
- おすすめ記事
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 に活用する方法も紹介しているので、ぜひチェックしてみてください!