committee-railsを使ってスキーマ駆動開発を始める

フロントエンドエンジニアとの連携

わたしはRailsでバックエンドAPIを実装しているのですが、開発当初、フロントエンドエンジニアから以下のような提案がありました。

「OpenAPIでスキーマを記述してお互いコンセンサスを取ってから実装しませんか?」

スキーマ駆動開発のことを言っているんだなと思い、

「いいと思うよ、それでいこう」

と答えました。答えたものの、やり方がわかっていないので、調べたことを記載していきます。

committee-railsを使う

“Rails スキーマ駆動開発”と調べると、いろんな例が出てきます。大体の事例がcommittee-railsを使っているので、これが良さそうです。早速インストールします。(テストのときにしか使わないのでtest groupに追加します)

1
2
3
4
5
6
7
8
9
10
$ git diff Gemfile
diff --git a/api/Gemfile b/api/Gemfile
--- a/api/Gemfile
+++ b/api/Gemfile
@@ -76,6 +76,7 @@ group :test do
gem "capybara"
gem "selenium-webdriver"
gem "webdrivers"
+ gem "committee-rails"
end
1
2
3
4
5
6
7
8
9
10
11
$ docker-compose run --rm api ./bin/bundle install
Using devise-i18n 1.10.2
Using devise_token_auth 1.2.1
Installing openapi_parser 0.15.0
Installing json_schema 0.21.0
Fetching committee 4.4.0
Installing committee 4.4.0
Fetching committee-rails 0.6.1
Installing committee-rails 0.6.1
Bundle complete! 29 Gemfile dependencies, 114 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

インストールが完了しました。

committee-railsの設定

公式のGitHubにも記載がありますが、spec/rails_helper.rbにcommittee-rails用の設定を追記します。
この設定を行うことで、assert_response_schema_confirmメソッドなどが利用できるようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git diff spec/rails_helper.rb
diff --git a/api/spec/rails_helper.rb b/api/spec/rails_helper.rb
--- a/api/spec/rails_helper.rb
+++ b/api/spec/rails_helper.rb
@@ -60,4 +60,12 @@ RSpec.configure do |config|
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
+
+ config.include Committee::Rails::Test::Methods
+ config.add_setting :committee_options
+ config.committee_options = {
+ schema_path: Rails.root.join("open_api.yaml").to_s,
+ }

specの追加

committee-railsが正しく動作するかどうか確認するための簡単なspecを用意します。devise_token_authでメールアドレス登録のAPIをテストします。(OpenAPIのスキーマは記載済です)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require "rails_helper"

RSpec.describe "API::Auth::RegistrationsController", type: :request do
describe "POST /api/v1/auth" do
it "returns 200" do
post "/api/v1/auth", params: { email: "example@example.com" }
assert_response_schema_confirm(200)
end

it "returns 422" do
post "/api/v1/auth", params: { password: "password" }
assert_response_schema_confirm(422)
end
end
end

specの実行

では実行します。

1
2
3
4
5
6
7
8
9
10
$ docker-compose run --rm api ./bin/rspec spec/api/v1/auth/registrations_controller_spec.rb

(省略)

Failures:
1) API::Auth::RegistrationsController POST /api/v1/auth return 200
Failure/Error: <p><%= link_to t('.confirm_account_link'), confirmation_url(@resource, {confirmation_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url']}).html_safe %></p>

ActionView::Template::Error:
Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true

エラーが出てしまいました。どうやらメールのドメインが未設定だったようで、以下のように設定しました。
config/environments/test.rb

1
+  config.action_mailer.default_url_options = { host: 'localhost:3000'}

再度実行します。

1
2
3
4
5
6
7
8
$ docker-compose run --rm api ./bin/rspec spec/api/v1/auth/registrations_controller_spec.rb

(省略)

API::Auth::RegistrationsController
POST /api/v1/auth
[DEPRECATION] Committee: please set query_hash_key = rack.request.query_hash because we'll change default value in next major version.
[DEPRECATION] Committee: please set parse_response_by_content_type = false because we'll change default value in next major version.

正常に実行はできましたが、DEPRECATIONの警告が出ています。
公式サイトをよく見てみると、https://github.com/willnet/committee-rails#normal-usage にちゃんと書かれています

1
2
3
4
5
6
7
def committee_options
@committee_options ||= {
schema_path: Rails.root.join('schema', 'schema.json').to_s,
query_hash_key: 'rack.request.query_hash',
parse_response_by_content_type: false,
}
end

query_hash_keyparse_response_by_content_typeの設定を追加します。

1
2
3
4
5
6
7
+  config.include Committee::Rails::Test::Methods
+ config.add_setting :committee_options
+ config.committee_options = {
+ schema_path: Rails.root.join("open_api.yaml").to_s,
+ query_hash_key: "rack.request.query_hash",
+ parse_response_by_content_type: false,
+ }

再度実行します。

1
2
3
4
5
6
7
8
9
10
11
$ docker-compose run --rm api ./bin/rspec spec/api/v1/auth/registrations_controller_spec.rb

(省略)

API::Auth::RegistrationsController
POST /api/v1/auth
returns 200
returns 422

Finished in 0.5116 seconds (files took 9.9 seconds to load)
2 examples, 0 failures

無事実行できました。

おまけ

OpenAPIのschemaでresponse200と422の場合を記述していますが、422のときには値が設定されない場合があります。その場合はそのパラメータにnullableを設定しないといけないのですが、nullableかどうかだけの違いのためにschemaを全て書く必要があり、大変だと思いました。allOfでoverrideできればいいのですが、できないようで、現実的には変更のある箇所のみを別で定義して、allOfで組み合わせるのが良さそうです。

まとめ

今回はcommittee-railsを使ってスキーマ駆動開発の環境を準備し、動作確認用のサンプルを実行するとこまで行いました。

今後スキーマ駆動開発を進めていくとより難しい問題に直面しそうな気がします。その時にはまた記載したいと思います。

参考図書