こんにちは、Misoca開発チームのいっしーです。
11月からMisocaにJoinしました。
10年ほど東京にいたので、未知の土地に新鮮な気持ちでいます。
名古屋の好きなところはB級グルメがたくさんあるところ、こわいところは学生時代に学んでいた関数型言語OCamlや自然言語の形式理論の話がナチュラルに通じてしまうところです。
さて、そんな私の初仕事であるMisoca API v3が先日リリースされました。
見積書や取引先をAPI経由で扱えるようになっています。
また、技術面ではGrapeとgrape-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
以下にファイルを置いています。
そして、以下のような実装になっています。
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.rb
をapp/controllers
直下に配置した場合は、 http://localhost:3000/swagger_doc を確認してください。
このアドレスをSwaggerUI右上のテキストボックスに入力することで、ドキュメントがSwaggerUIで表示されます。
完成!
以上を行なってSwaggerUIで表示されたものがこちらです。
説明文やリクエストパラメータ、サンプルレスポンスなどがドキュメントに反映されていることがわかると思います。
実際に動くものがみたい方はこちらでどうぞ。
苦労した点
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をどんどん提供していきますので、お楽しみに!