ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

Rails + grape + Swagger UIでアプリケーション開発を円滑に進めるAPIサーバーを構築する

こんにちは、サーバーサイドエンジニアの本間です。

以前弊社のしだより、デジタル連絡帳アプリ るくみーnoteクライアントサイド開発の一部 をご紹介させていただきました。 今回は、このプロダクトのサーバーサイド開発で工夫した点をご紹介したいと思います。

この開発におけるサーバーサイドエンジニアの主なタスクは、デジタル連絡帳のデータを保管するためのAPIサーバーの構築です。 今回、アプリとサーバーは完全分業で並列に開発を進めることから アプリケーション開発者にいかに早くAPIの仕様や動作を理解してもらえるか が開発を円滑に進める上での鍵になりそうだなと考えていました。

この問題に対応するため、以下の2つの対策を可能な限り工数をかけずに実施したいと思い、調査しました。

  • わかりやすいドキュメンテーションの用意
  • 簡単にAPIを試すことができる環境の整備

Swagger UI

上記の要件を満たすツールとして Swagger UI が使えるのではないかと考えました。

Swagger UIは、Swagger形式(APIの仕様を表すドキュメンテーション形式の1つ)で書かれたファイルを読み込んで、ブラウザ上でAPIの仕様や動作確認ができるツールです。 公式のライブデモ環境 を見ていただくとイメージが掴みやすいかと思います。 各APIのリクエストやレスポンスの仕様がブラウザ上で確認できるほか、実際にAPIにリクエストを送って挙動を確認することもできます。

また、WebアプリケーションのフレームワークにはRailsを使うことが決まっていたのですが、下記のOSSライブラリを使用することでSwagger UIと連携したAPIサーバーを容易に構築できそうということがわかってきました。

今回の開発は、これらのツール、ライブラリを組み合わせてAPIサーバーを構築しました。 その内容をサンプルアプリケーションを使って説明しようと思います。

実施項目

サンプルアプリケーション

https://bitbucket.org/unifa-public/grape_swagger-ui_sample に公開してあります。

Ruby 2.3.0以降とbundlerがインストールされているPCであれば、以下のコマンドを実行することで確認できます。

$ git clone https://bitbucket.org/unifa-public/grape_swagger-ui_sample.git
$ cd grape_swagger-ui_sample
$ bundle install
$ bin/rails db:migrate
$ bin/rails db:seed
$ bin/rails s

立ち上がったら http://localhost:3000/swagger にアクセスすれば、下記のようなSwagger-UIの画面を表示する事ができます。

f:id:ryu39:20170331114116p:plain

各APIをクリックするとリクエストやレスポンスの詳細が表示されます。 「Try it out!」のボタンを押すと実際にAPIにリクエストを投げて、挙動やレスポンスを確認することもできます。

構築に必要な作業一覧

以下が必要な作業一覧になります。作業量は非常に少なく、これだけで前述したSwagger UIと連携したAPIサーバーを作ることができます。

  1. Railsアプリケーションを作り、 Modelを作成
  2. grape関連のgemを追加し、bundle install
  3. app/apis ディレクトリを新規に作り、API定義と実装のファイル(users.rb)、APIのレスポンス定義のファイル(xx_entity.rb)を用意
  4. API定義と実装のファイル(users.rb)にSwaggerドキュメント生成の設定を追加
  5. config/initializer/grape_swagger_rails.rb でgrape-swagger-railsを使うための設定を実施
  6. config/routes.rb でgrapeとgrape-swagger-rails用のルートを追加

APIの定義

Swagger UIでAPIの情報を表示するためにどんなコードを書いているかも説明いたします。

例として「User 1件作成」のAPIを見てみます。以下は、Swaggerのドキュメントに関係ある部分だけを抽出しています。

class Users < Grape::API
  # pathが/users
  resources :users do
    desc 'Create a user' do
      # リクエストヘッダーで指定可能な内容
      headers({ :'X-Custom-Header' => { description: 'custom header', required: false } })
      # 処理成功時のレスポンスの内容。UserEntityは app/apis/user_entity.rb に定義されている
      success UserEntity
      # 処理失敗時に返す可能性があるステータスコードとレスポンスの内容
      failure [
                [400, 'Validation error', ErrorEntity],
                [500, 'Server error', ErrorEntity],
              ]
    end
    # リクエストパラメーターで指定可能な内容
    params do
      requires :name, type: String, documentation: { param_type: 'body' }
      requires :age, type: Integer, documentation: { param_type: 'body' }
      optional :emails_attributes, type: Array, documentation: { param_type: 'body' } do
        requires :mail_address, type: String, documentation: { param_type: 'body' }
      end
    end
    # POSTメソッド
    post do
      # 実装コード
    end
  end
end

success で指定している UserEntity や、 params ブロックで指定している各パラメーターの定義は、APIの実装でも使用しています。 そのため、APIの実装をするだけでリクエストやレスポンスに関する最低限のドキュメントを用意することができます。 あとは、ヘッダーや処理失敗時の説明を追加するだけで、十分な情報をもったドキュメントを自動で生成することができます。

上記のAPIをSwagger UIで見ると以下のようになります。 リクエストやレスポンスの仕様の確認の他、実際にリクエストを投げてみてデータを作成することもできます。

f:id:ryu39:20170403200900p:plain

APIは合計で80個ほどあるのですが、全てで上記のようなコードを書いています。 実装と共通のコードが多いため、工数はそれほどかからずに対応できました。

このSwagger UIを開発初期はアプリケーションエンジニアの方のローカルで、中盤以降は共通の開発サーバーで動かしました。 開発終了後、アプリケーションエンジニアの方と振り返り会を行ったのですが、「実際に動かして挙動を確かめられたのは開発にかなり役に立った」というコメントをいただくことができました。

今後に向けて

好評だったSwagger UIでのAPIの仕様や動作の確認ですが、開発が進むにつれ、いくつか問題があることがわかってきました。

OAuth 2.0の認可制御への対応

開発の途中からOAuth 2.0のフローに沿った認可制御をかけるようにしました。 これによりAPIの動作確認にもアクセストークンがないとエラーが発生してしまうようになりました。 事前にcurl等のコマンドでアクセストークンを取得し、Authorizationヘッダーに設定すれば使用できるのですが、利用までの作業が多く利便性が大きく下がってしまいました。

この解決手段として、Swaggerの仕様に securityDefinitionssecurity という項目があります。 これらを適切に設定することでSwagger UI上でも簡単にアクセストークンの取得、設定ができるようになるようなので、早期に導入したいと考えています。

swagger上のレスポンス定義と実際のレスポンスの一致

今回のサンプルアプリケーションのSwagger UIで「ユーザー一覧(/users)」のレスポンスは、定義と実際の内容が異なっています。 具体的には emails が定義には含まれているのに、実際のレスポンスには含まれていません。

これは、一覧取得系のAPIではサーバーやネットワークの負荷を考え emails の値を意図的に抜いているのですが、その内容がSwaggerの方に反映されていません。 この対応をするためには、emails を抜いたEntityと含めたEntityをそれぞれ定義して、これを適切な場所で参照する必要があります。 grape-entityの継承を適切に使用すれば、比較的容易に対応できると考えています。

# こちらはユーザー一覧で使用
class UserEntity < BaseEntity
  expose :id, documentation: { type: Integer, desc: 'ID' }
  expose :name, documentation: { type: String, desc: '名前' }
  expose :age, documentation: { type: Integer, desc: '年齢' }

  with_options(format_with: :iso_timestamp) do
    expose :created_at, documentation: { type: DateTime, desc: '作成日時' }
    expose :updated_at, documentation: { type: DateTime, desc: '更新日時' }
  end
end

# こちらはユーザー1件取得、作成、更新で使用
class DetailedUserEntity < UserEntity
  expose :emails, using: EmailEntity, documentation: { is_array: true, desc: 'メールアドレス一覧' }
end

エラーコードの対応

実際のAPIではエラーメッセージに加えエラーコードも返しています。しかも、エラーコードは同じステータスコードに対して複数あります。

エラーコードはアプリケーション側で適切なエラー処理を行うために非常に大切な情報なのですが、現状この情報は以下のように無理やり説明に押し込んで表示している状態です。

f:id:ryu39:20170403203141p:plain

明らかに見づらいため改善したいのですが、Swaggerの仕様内ではうまい解決方法がないと思われるので、対応方法を検討中です。

クライアントライブラリの自動生成

Swaggerには Swagger Codegen というクライアント側のコードを自動生成してくれるツールがあります。

今回の開発では、Swagger形式のドキュメントの内容とAPIの挙動に少しズレがあったため採用は見送っていたのですが、ここに書いた問題が修正できれば利用できるかもしれないと考えています。 iOS/Androidではすでにクライアントのコードが存在するため置き換えは難しいですが、例えば今後PC用の画面をAPIを使って作る、となった場合にJavaScriptのコードを自動生成するといった使い方ができると考えています。

まとめ

今回、Rails + grape + Swagger UIでアプリケーション開発者に優しいAPIサーバーの構築方法を紹介いたしました。 これからAPIサーバーを構築するサーバーサイドエンジニアの方の参考になれば幸いです。

それでは最後までご覧いただきありがとうございました。