RailsでGraphQL APIを実装するとき、Railsエンジンを使うと便利
はじめに
マイベストでフロントエンドエンジニアをしているteppeitaです
(今回はRailsの話ですが、業務でRailsのコードも書いています)
マイベストではGraphQLを採用していて、バックエンドをRailsで実装しており、ライブラリとしてはgemのgraphql-rubyを利用しています。
(graphql-rubyの詳細について書き始めると長くなってしまうので割愛します)
GraphQLの設計に関して色々と試行錯誤している中で、Railsエンジンと組み合わせるとコードを柔軟に分割できそうだ、という発見が有ったのでその話をしたいと思います。
Railsエンジンとは
Railsエンジンについては、簡単に説明すると、メインのRailsアプリケーション内に、別の名前空間で、MVCを含むアプリケーションを用意することができるもので、
例えば管理画面をエンジンで作ることによって、管理画面内でのみ共通で使いたい処理をエンジンの中にまとめることができたりします。
(※使いすぎには注意です)
Railsエンジンを使った分割
Railsエンジンを使うと名前空間を分離できます。
これがGraphQLを使っていて困ることに対して有効に使えるケースが有るので紹介します。
GraphQLで困ること
例えば、同じArticleで、Webとアプリでタイトルを変えたいことが有ります。(WebはSEO用のキーワードを入れたい、など)
あるいは、管理画面では入力が無ければ空にしたいけど、ユーザーが見る画面(以下フロント画面と呼びます)ではデフォルトタイトルを表示したいケースなど。
こんな時は名前空間が使えれば良いなと思います。
ところで、Production Ready GraphQLという良書が有るのですが、
そこでは「命名を頑張れば、名前空間はほとんど必要ない」と書かれていました。(超意訳)
(どんな本なのかは以下の記事に詳しいです)
確かに命名が上手くできれば(=それぞれのfieldに対して適切で具体的な名前をつけられれば)、問題にはならなそうです。
が、ゆるふわな命名をしてしまうとどうなるかというと、、、
class ArticleType < BaseType
field :title, String, null: true # 管理画面用
field :title_for_web, String, null: false
field :title_for_mobile, String, null: false
end
こうなります。
実装当初は問題無いかもしれません。要求に従って必要なfieldを定義しただけなので、余計な混乱は起きづらいです。
しかし、時が経って別の要求によりこのfieldを使いたい時に混乱します。
例えば「新しく作ったリストに、タイトルを表示したいからtitleを使ったらnullが返ってきた」というような問題が起きます。
そして残念なことに、命名スキルや時間の問題で、こうした命名をしてしまうことも往々にしてあるのではないでしょうか。(私はあります😇)
そこで、残念な命名スキル 時間の問題を補うために、Railsエンジンを使った分割をするとどうなるか紹介します。
エンドポイントを分ける
まずRailsエンジンの名前空間ごとに、エンドポイントを分けることができます。
エンジン上にcontrollerを作成し、そこから呼び出すschemaを専用のものにすることで、エンドポイントごとに呼び出せるroot queryを変えることができます。
(/graphql
と/admin/graphql
のように)
module Admin
class GraphqlController < ApplicationController
def execute
...
result = Admin::HogeSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
...
end
end
end
module Admin
class HogeSchema < GraphQL::Schema
query(ObjectTypes::AdminQueryType)
mutation(ObjectTypes::AdminMutationType)
end
end
これによって管理画面とそうでないフロント画面で処理を分けることができるのですが、
例えば以下のようなことが名前空間ごとに簡単に実現できます。
- フロント画面が表示専用な場合は、mutationを実行できないようにする
- フロント画面でのみURLのパラメータによる分岐処理を追加する
- base_resolverをそれぞれ作成し、管理画面でのみ認証を必須にする
typesを分ける
typesもエンジンごとに分けることができます。
先述のfield命名に関しても以下のように分けることができます
module Admin
class ArticleType < BaseType
field :title, String, null: true
end
end
module Front
class ArticleType < BaseType
field :title, String, null: false # 空の時はデフォルト値を返す
end
end
これで先ほどの命名問題は解決しました(?)
一方で、typesをメインのアプリケーション(/app)に置くことで、共通化する、という選択肢も有ります。
例えば、schemaやroot queryは別、root fieldとそのresolverは別、object_typesは共通、
というように、共通部分と分離する部分を柔軟に設計可能です。
まとめ
Railsエンジンを使うとshcemaを柔軟に分割できます。
一方で、GraphQLはなるべく分割しない方が良い、命名を頑張れば分割しなくても運用できる、という話も有ります。
私としては、組織によっては命名が上手くいかないのっぴきならない事情も有ると思うので、そういった場合には、名前空間の分離によって腐敗を防ぐのも現実的・合理的だと思います。
本記事では、そういったケースに対応するためにRailsエンジンという手段が有りますよ、という紹介をしました。
(※もちろん、良い命名ができるように努力し続けるのは必須で、「名前重要」がエンジニアの基本姿勢だと思っています。)
弊社では現在その辺りのschema設計をどうしていくべきなのか運用しながら試行錯誤中ですが、良い形が見つかればまた記事にしたいと思います。
株式会社マイベストのテックブログです! 採用情報はこちら > notion.so/mybestcom/mybest-information-for-Engineers-8beadd9c91ef4dc2b21171d48a4b0c49
Discussion