Grape+SwaggerでAPIのドキュメント作成を自動化した

こんにちは、Misoca開発チームのいっしーです。
11月からMisocaにJoinしました。

10年ほど東京にいたので、未知の土地に新鮮な気持ちでいます。
名古屋の好きなところはB級グルメがたくさんあるところ、こわいところは学生時代に学んでいた関数型言語OCaml自然言語の形式理論の話がナチュラルに通じてしまうところです。

さて、そんな私の初仕事であるMisoca API v3が先日リリースされました。
見積書や取引先をAPI経由で扱えるようになっています。

www.misoca.jp

また、技術面ではGrapegrape-swaggerを新たに導入しました。
今回はこれらについて紹介したいと思います。

背景

Misoca API v1 (以後、v1とします) では、Rails標準のControllerでリクエストを受け付け、結果をjson形式などでレスポンスを返す形を取っていました。また、APIドキュメントは手書きしていました。

問題点

やはりというべきか、v1では変更への追従がひとつの課題でした。
Web版Misocaの仕様変更をAPIに反映させることも、APIの変更をドキュメントへ反映させることも、毎回漏れなく行うには結構な手間がかかりました。

もっと楽したい……。

Grape+Swaggerへの移行

上記の問題を解決するために、まずはドキュメントの作成を自動化することにしました。 コードからのドキュメントの生成には、AutodocやApipie-railsなどがありましたが、標準化されたフォーマットということでSwaggerを採用しました。SwaggerではSwaggerUIと呼ばれるAPIドキュメントビューワーも提供されています。
コードの実装には、Grapeを使用しました。GrapeはRubyでRESTlikeなAPIを実装するためのフレームワークで、コードをきれいに記述できたり、APIのルーティングをDSLで記述することができます。

今回、Grape+Swaggerで実現したいことは以下の二つです。

  • コードからドキュメントを自動生成できる
  • サンプルレスポンスを表示できる

gemのインストール

まずは、以下のgemをインストールしました。

gem 'grape'                   # Grapeを使うときのGem
gem 'grape-entity'            # 1対多のデータ構造を書くときに使う
gem 'grape-swagger'           # Grapeで定義したAPIをSwagger形式でドキュメント化するために使う
gem 'grape-swagger-entity'    # レスポンスモデルをSwaggerで見られる形式にするときに使う

APIとドキュメントの実装

次にAPI本体を実装します。
v3のクラスはApplicationControllerを継承しませんが、以下の理由からapp/controllers/api/v3 以下にファイルを置いています。

  • 役割的にはcontroller
  • 他のAPIと同じような場所に置きたかった
  • autoloadの効くディレクトリに置きたかった

そして、以下のような実装になっています。

module Api
  module V3
    module Estimates
      class Read < Grape::API
        # 共通メソッドのinclude。今は以下のようなメソッドが定義されている
        #   - desc_scope(scopes, desc, **args)
        #   - current_party/current_user
        #   - 有効な access_token を持っているかの判定
        include Api::V3::Authenticate

        # Grape記法でAPIの説明を書く
        desc '見積書'
        resource :estimate do
          desc_scope %w(read write), '見積書を取得します', success: ApiEntity::Estimate
          params do
            requires :id, type: Integer, desc: '見積書のID'
          end
          route_param :id do
            get do
              # API本体の実装
            end
          end
        end
      end
    end
  end
end

descで記載している部分が、APIの説明としてSwaggerで表示されます。

レスポンススキーマの実装

そして、サンプルレスポンスを自動生成するために、app/presenter/api_entity以下にレスポンススキーマを定義します。

module ApiEntity
  class Estimate < Grape::Entity
    class Item < Grape::Entity
      expose :name, documentation: { desc: '品目' }
      expose :unit_price, documentation: { desc: '単価', type: 'number' }
      expose :quantity, documentation: { desc: '数量', type: 'number' }
      expose :unit_name, documentation: { desc: '単位' }
      expose :tax_exempted, documentation: { desc: '消費税対象外フラグ', type: 'boolean' }
    end

    class Body < Grape::Entity
      ...

SwaggerUIへの出力

最後に、API本体をapp/controllers/api/v3/root.rb内でマウントし、Swagger形式のドキュメントを作成します。

module Api
  module V3
    class Root < Grape::API
      format :json
      ...

      # APIをマウントする
      mount Api::V3::Contacts::Read
      mount Api::V3::Contacts::Write
      mount Api::V3::Estimates::Read
      mount Api::V3::Estimates::Send
      mount Api::V3::Estimates::Write

      # swaggerドキュメントを生成する
      add_swagger_documentation(
        ...
      )
    end
  end
end

ここで生成されたjson形式のドキュメントは http://localhost:3000/api/v3/swagger_doc で確認することができます。
root.rbapp/controllers直下に配置した場合は、 http://localhost:3000/swagger_doc を確認してください。

このアドレスをSwaggerUI右上のテキストボックスに入力することで、ドキュメントがSwaggerUIで表示されます。 f:id:issi_0x0:20161220102814p:plain

完成!

以上を行なってSwaggerUIで表示されたものがこちらです。
説明文やリクエストパラメータ、サンプルレスポンスなどがドキュメントに反映されていることがわかると思います。 f:id:issi_0x0:20161216101208p:plain

実際に動くものがみたい方はこちらでどうぞ。

苦労した点

GrapeはApplicationControllerを継承していないため、そこで定義していた変数が一部使えなくなりました。 具体的には、現在ログインしているユーザを表すcurrent_userなどです。 これらは認証を行うために必要なので、別途、実装しました。

認証情報を取得する

認証を行うために、grape helperを使ってGrapeの拡張を行います。いまのところv3でしか使わないので、app/controllers/api/v3以下にauthenticate.rbを作成し、その中でcurrent_userなどを定義します。

require 'doorkeeper/grape/helpers'

module Api
  module V3
    module Authenticate
      extend ActiveSupport::Concern
      ...

      included do
        helpers Doorkeeper::Grape::Helpers
        ...

        helpers do
          def current_user
            ...
          end

          ...
        end
      end
    end
  end
end

まとめ

APIドキュメントを自動生成するようにしたことで、コード修正とドキュメント修正の二重苦から解放され、保守がかなり楽になりました。
ドキュメント生成の時間が削減できた分、新しいAPIをどんどん提供していきますので、お楽しみに!