Linc'well Advent Calendar3日目の記事です。
当社が展開するクリニックグループである CLINIC FOR の予約システム では一部機能にGraphQLを導入しているのですが、スキーマの管理等も踏まえてどのように構築し、運用したかについて紹介したいと思います。
※ ちなみに構築したのは2019/3~4頃であり、その点は割り引いてご覧ください
今だともっと良さげなプラクティスがあるかもしれません。
また、予約システム全体としてはモノリシックなRailsアプリケーションになっているのですが、GraphQLを導入した一部機能ではフロントエンドがTypeScript+Reactで構築されており、ruby側で定義されたスキーマ情報はTSの型定義へスムーズに繋げる必要がありました。
スキーマファイルの生成
GraphQLには様々な実装があるかと思いますが、今回rmosolgo/graphql-ruby
というgemを利用しました。
こちらのgemはスキーマの定義ファイルを直接記述せず、個別のクエリや型の実装から最終的なスキーマを出力する code-first なアプローチを取っています。
なので処理を先に記述し、それを参照することで型定義が決定される、という順番になるのですが、最終的な定義の情報は、Schemaのクラスに生えている以下のメソッドで文字列として全出力され、その戻り値をファイル出力してあげることでスキーマファイルが出来上がります。
今回は上記メソッドを以下のようなrakeタスクに組み込み、差分が生じたらファイル出力の上でコミットしていくという運用で行くことにしました。
# rake graphql:dump_schema
namespace :graphql do
task dump_schema: :environment do
schema_definition = LincwellSchema.to_definition
schema_path = 'frontend/src/generated/schema.graphql'
File.write(Rails.root.join(schema_path), schema_definition)
puts "#{schema_path} updated."
end
end
CIでのスキーマ定義チェック
定義ファイルが最新のものかチェックするため、rspecにてスキーマファイルの最新チェックも用意します。
describe 'Validate lastest-veresion' do
let(:dump_path) { Rails.root.join('frontend', 'src', 'generated', 'schema.graphql') }
let(:current_definition) { File.read(dump_path) }
subject { described_class.to_definition }
it { is_expected.to eq(current_definition) }
end
簡易な実装ですが、CIが回るたびにチェックが入るので、少なくともこれで更新忘れはなくなり、実装とスキーマの乖離を防止することができたのではないかと思います。
スキーマ情報からTSの型をつくる
続いてはこうして出来上がったスキーマ情報をフロントエンド側へ連携させていきます。
出力されたスキーマ情報をもとにTSの型を作りたいのですが、さすがに手ずから行うのは厳しいので graphql-codegen
を用いて自動化することにします。
# codegen.yml
overwrite: true
schema: "src/generated/schema.graphql"
documents: "src/libs/queries/*.ts"
generates:
src/generated/graphql.tsx:
plugins:
- "typescript"
- "typescript-operations"
- "typescript-react-apollo"
src/generated/graphql.schema.json:
plugins:
- "introspection"
"scripts": {
"generate": "graphql-codegen --config codegen.yml"
}
上記設定の上、 npm run generate
コマンドを実行します。
そうするとrailsから出力されたスキーマファイルを元に、 src/generated/graphql.tsx
にTSの型定義が自動生成されるようになります。ここまで繋がると気持ちイイですね!
これでReactコンポーネントから自由に任意のクエリやオブジェクトをTSの型として利用できるようになりました。Rails側で定義されている graphql/types
と乖離しないため、非常に楽に、かつ楽しく開発を進めることができます。
{
"paths": {
"Components/*": ["src/components/*"],
"Styles/*": ["src/styles/*"],
"Libs/*": ["src/libs/*"],
"Generated/*": ["src/generated/*"]
}
}
}
tsconfig.jsonで上記のようにaliasを切り、Componentで以下のように呼び出せます。
import * as React from "react";
import { useQuery } from "react-apollo-hooks";
import styled from "styled-components";
import { CancelRate } from "Generated/graphql";
めでたしめでたし
ちなみにフロントエンド側の型生成まわりに関しては以下の記事を大変参考にさせて頂きました。
投稿時期も非常にタイムリーで、もしこのエントリがなかったらと思うと(ry
まとめ
- code-firstな
graphql-ruby
の利用 -
#to_definition
をrake経由で実行し、型定義の.graphql
ファイルをフロントへ配置 -
rspec
にて最新dumpの検査 -
graphql-codegen
にてTSの型定義ファイルへコンバート - フロントアプリケーションにて利用可能に
だいたいこんな感じになりました。
現状もこの当時からあまり大きく変わっていませんが、もしより良いプラクティスがあればフィードバック頂けたら幸いです。