💍

RailsでもOpenAPIドキュメントを生成したい!

2024/12/07に公開

本記事は 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と互換になったようなので、今後はより意識せずに運用できるようになりそうです。

最後まで読んでいただきありがとうございました。

SimpleForm Tech Blog

Discussion