RailsでもOpenAPIドキュメントを生成したい!
本記事は SimpleForm Advent Calendar 2024 の 7 日目の記事です。
スキーマ駆動開発や外部公開APIドキュメントをOpenAPI(swagger)にて作成している方も多いと思います。
一方でOpenAPIを作成・運用するのは記述量が多かったり、軽微な変更時に修正漏れが発生してしまうなどコストが高く、特に外部へ公開しているAPIについて発生した場合は利用者に迷惑をかけてしまうリスクがあります。
今回はRailsにて提供しているAPIにおいて、OpenAPIと実装が乖離していることを防ぎつつ、アプリケーションコードとして管理できるようにした弊社での実装例を紹介します。
課題感と達成したいこと
OpenAPIと実装が乖離した場合に気づけること
弊社では外部向けAPIサーバーをAPI Gateway + Lambdaで構築し、OpenAPIは別途Flask経由にて作成をしていました。
OpenAPIが別実装として管理していたため、Lambdaの実装が変更された際にOpenAPIが追従できていないことが度々ありました。
Railsでドキュメント生成ができること
弊社では顧客提供しているWebアプリケーションはRailsで実装されており、外部向けAPIが別実装であることが課題となっていたため、今回Railsへ移植することを決定しました。
一方で、これまでWebアプリケーション内部向けのAPIはOpenAPIを作成していなかったこともあり、移植の際に改めてOpenAPIの作成方法について再検討する必要がありました。
採用したgemについて
構造としてはTandemsさんの記事とほぼ同様の構成をとっています。
rswagの中に巨大な構造のrequest/responseスキーマが存在してしまうと、rspecそのものの可読性が落ちてしまうため、json-schema_builderを利用してrequest/responseスキーマを別ファイルに切り出しています。
弊社では別リポジトリにて、APIレスポンスの一部の構造をjson_schemaにて出力できる構造となっており、そちらも流用しながらOpenAPIを生成できることもメリットでした。
他に検討したgem達
-
committee-rails
committee-railsはOpenAPIを別で実装し、OpenAPIとの乖離がないかrspecにてテストを行うツールとなります。
弊社のAPIでは複雑な構造のcomponents/schemas
を大量に手動で記載する必要があるために、API手動で書くことが難しいこと、
OpenAPIを書くための周辺ツール群を整備すると実装時の切り替えコストが高くなってしまうため、見送りました。 -
rspec-openapi
既存request specに少し手を加えるだけでOpenAPIドキュメントが生成できること、components/schemas
を自動でまとめてくれることも魅力的ではあったのですが、既存APIの置き換えということもあり、細かい設定が必要なケースもあるため断念しました。
構造について
ディレクトリ構成
既存のrequest specはそのままに、spec/api/
配下にrswagファイルを置いています。
repository-root
├ app
│ └ controllers
│ └ external_api
│ └ api_controller.rb # api実装
├ docs
│ └ openapi.yaml # 自動生成されるOpenAPI
└ spec
├ swagger_helper.rb # rswagのベース設定とスキーマをマージします
├ api # rswagに関するディレクトリ
│ └ {api_path}
│ └ {method}_{api_path}_spec.rb # rswagによるドキュメント生成とスキーマテスト
├ requests
│ └ api_controller_spec.rb # 既存同様のrequest spec
└ schemas # json-schema_builderに関するディレクトリ
├ root_schema.rb # json-schemaを取りまとめる
├ autogen # 別リポより自動生成されたjson-schema
│ ├ json_schema.json
├ request
│ └ get_api_request_schema.rb
└ response
└ get_api_response_schema.rb
共通設定ファイルについて
rswagやjson-schema_builderの記述方法については公式ドキュメントの方が詳しいため、ここではカスタマイズしている共通設定ファイルについてのみ記載します。
spec/schemas/root_schema.rb
RootSchemaClassを作成し、作成したRequest/Responseスキーマ及び、別途生成されたjson-schemaをマージしています。
schema一覧として管理しやすいように別ファイルに切り出しています。
# frozen_string_literal: true
require 'json-schema'
class Schema::RootSchema
def schema
{
# Request
getApiRequest: Schema::Request:Api.new.schema.as_json,
# Response
getApiResponse: Schema::Response:Api.new.schema.as_json,
}.merge(
Schema::AutoGen.new.schema.reduce({}, :merge),
)
end
end
spec/swagger_helper.rb
rswagからrefできるように、schemasへ追加するようにしています。
# frozen_string_literal: true
require 'rails_helper'
require 'json/schema_builder'
JSON::SchemaBuilder.configure do |opts|
opts.validate_schema = true
opts.strict = true
end
require Rails.root.join('spec/schemas/root_schema.rb')
Dir[Rails.root.join('spec/schemas/**/*.rb')].sort.each { |f| require f }
schemas = Schema::RootSchema.new.schema.as_json
RSpec.configure do |config|
config.openapi_root = Rails.root.join('docs/').to_s
config.openapi_strict_schema_validation = true
config.openapi_specs = {
'openapi.json' => {
...
schemas: schemas, # ここでjson-schemaをopenapiにマージしています
},
}
end
これらのファイルによってAPIのrswagからは以下のように呼び出すことが可能となっています
schema '$ref' => '#/components/schemas/GetApiResponse'
json-schema_builderを採用する場合の注意事項
たとえば、OpenAPI 3.0ではドキュメントに記載のような差分があります。
おわりに
テストコードそのものは通常request specよりも多く書かなければならないものの、
ドキュメントそのものの記載漏れや、実装と乖離した場合に気づきやすいなどの恩恵が大きいと感じています。
OpenAPIのバージョンによって対応するjson schemaのバージョンが異なっているのですが、
OpenAPI 3.1.0にてjson schemaと互換になったようなので、今後はより意識せずに運用できるようになりそうです。
最後まで読んでいただきありがとうございました。
リアルタイム法人調査システム「SimpleCheck」を開発・運営するシンプルフォーム株式会社の開発チームのメンバーが、日々の開発で得た知見や試してみた技術などについて発信していきます。 Publication 運用への移行前の記事は zenn.dev/simpleform からご覧ください。
Discussion