Web APIを作る上で考えていること
ここ数年、Web API(以降API)を作る機会が増えています。例えば、スマホやシングルページアプリケーションのバックエンド,システム間のデータ連携などです。 これらのAPIを複数の言語やフレームワークで実装してみましたが、個々の機能を実装する前に考えるべき事は共通していました。これから先も多くのAPIを作成する事になると思いますので、APIを作る上での共通実装をまとめてみたいと思います。
目次
- 文字コード
- 日付書式
- リクエストフォーマット
- レスポンスフォーマット
- API認証
- Validation
- トランザクション
- ログ出力
- 横断処理
- 共通例外処理
- 設定ファイル
- 処理時間の計測
- X-Request-ID
- ヘルスチェック
- 単体テスト
- デプロイ
検討項目
文字コード
UTF-8を選択します。
日付書式
ISO8601形式を選択します。 曖昧さを無くすためタイムゾーン情報を付与したいためです。
例: 2015-04-07T09:00:00+0900
リクエストフォーマット
POST・PUTのリクエストボディはJSONを選択します。
レスポンスフォーマット
JSONを選択します。
API認証
APIの認証方法とリソースの権限制御の仕組みを考えます。 それぞれ一長一短がありますので、要件によって適切な方法を選択します。
...
Validation
入力されたパラメータを簡単に検証するための仕組みを考えます。 RailsだとActiveRecordの標準機能、JavaだとHibernateValidatorなど、メタ情報から検証出来るものを選択します。 また、検証に失敗した項目を構造化したレスポンスで返せる用に拡張します。
トランザクション
業務ロジックの正常終了・異常終了を判断して自動的にコミット・ロールバックする仕組みを考えます。 これを導入しておくと、トランザクションの張り忘れやロールバック忘れなどの些細な不具合が少なくなります。
ログ出力
ログ出力先:
- ファイル
- データベース
- Fluentd
ローテーション周期:
- 日次
- 週次
- サイズ
ローテーション手段:
- Loggerの機能
- OSのlogrotate
出力フォーマット:
共通のログ出力:
- リクエスト・レスポンスのDEBUGログ
- SQLのDEBUGログ
...
横断処理
業務ロジックの前後に処理を入れる仕組みを考えます。 別項にあるトランザクション・共通例外処理・処理時間計測・X-Request-IDの付与などで使います。
...
共通例外処理
アプリケーションの業務ロジックで発生した例外を適切に処理するポイントを考えます。 APIの場合、例外によってレスポンスを選択することが多いです。
- 不正なリクエスト・Validationエラー: 400 Bad Request
- 認証失敗: 401 Not Authorized
- リソースに対する権限なし: 403 Forbidden
- リソースが存在しない: 404 Not Found
- リソースの競合: 409 Conflict
- サポートされていないメディア・タイプ: 415 Unsupported Media Type
- その他の例外: 500 Internal Server Error
...
設定ファイル
開発・ステージング・本番環境など、各種環境で設定ファイルを切り替える方法を考えます。
処理時間計測
パフォーマンスを測定するために、リクエスト毎に処理時間を計測します。
X-Request-IDのログ埋め込み
障害発生時や関連システムからの問い合わせなどで対象のリクエストを一意に識別するために、レスポンスにX-Request-IDとしてUUIDを付与します。
ヘルスチェック用URLの用意
ロードバランサーなどから監視するヘルスチェック用のURLを用意します。 DBなど、APIの提供に必要なミドルウェアへの疎通に成功したら正常ステータス(200)を返す処理にします。
余談ですが、外部からの設定でヘルスチェック用URLのレスポンスを200 or 503と切り替える機能を付けると、アプリの設定でロードバランサーから切り離すことが出来るようになるため便利です。
単体テスト
選択するフレームワークや組み合わせるライブラリによって、特定のオブジェクトを生成することが難しいなどの理由で、単体テストの実装が難しくなってしまうことがあります。また、初期の段階で導入しておかないと、テストを意識した設計ができずにテストし辛いクラスが出来あがってしまうことがあります。 私の場合、忙しさに追われて単体テストを書かないという状態に陥ってしまったことがあり、その際にソフトウェアの品質が著しく下がってしまった経験から、単体テストを実行する仕組みを最初に整備しておくことにしています。 また、この段階からJenkinsなどのCIサーバと連携するために、CUIから実行できるようにします。
- Controllerのテスト
- Modelのテスト
- Viewのテスト
- Mock/Stub
- 生成が困難なオブジェクト
- DIコンテナ
- HTTP通信
...
デプロイ
稼働するサーバに向けてのデプロイ方法を考えます。 後々JenkinsなどのCIサーバと連携できるようにCUIから実行できるようにします。
おわりに
最近、これらの事をRailsで実装する機会があったのですが、驚くほど簡単に実装できてしまいました。 Javaで複数のライブラリや設定ファイルを組み合わせて実装していた頃にはもう戻れませんね。