API クライアント実装時の注意点
はじめに
駆け出しの Web 系エンジニアに API クライアントの実装に関する質問を受け、一定以上の経験を持つエンジニアが自然と実践しているいくつかのプラクティスは、当たり前がゆえにあまり語られていない部分なのかもしれないと思いました。
本記事は、普段自身が API クライアントの実装時に意識していることを、質問者への回答と自身の理解の整理も兼ねて書き留めたものです。なお、本記事における API クライアントとは基本的に RESTful API または RESTish API のクライアントを指すものとします。
もしも HTTP リクエストメソッドや HTTP ヘッダ、 HTTP ステータスレスポンスコードといった API クライアントを実装する上で最低限必要となる知識について不安のある方は、あわせてそれらを説明している Web ページや書籍を参照することをおすすめします。
API 提供者と合意する
実装以前の話になりますが、社内のチームや協力会社など、API の提供者と連絡を取れるのであれば、API の利用を開始する前にその内容について合意しておくと手堅いです。また、合意したときの内容を Pull Request の概要欄やタスクのチケットなどに記録しておくと尚良です。
あらかじめ以下のような情報を提供し、提供者に利用可否を判断してもらった上で利用を開始しましょう。
- 利用するエンドポイント
-
平常時の秒間リクエスト数とピーク時の秒間リクエスト数
API の提供者が API サーバのキャパシティ的に問題がないかを判断する材料になります。 -
User-Agent や利用元を識別する情報
API の提供者が今後利用状況を調査・集計する際に役立つ情報になります。 -
アクセス元の IP アドレス
API サーバが ACL を設定している場合に IP アドレスを許可してもらう必要があります。 - 利用開始日時
-
連絡先
コミュニケーションツール上のユーザ名やグループ名など連絡を受ける窓口になります。 - その他役に立ちそうな任意の情報
提供者と連絡を取れる状況にも関わらず、連絡せずにいきなり API を利用し始めることはできる限りやめましょう。API サーバのキャパシティによっては障害に繋がったり、関係者との関係に良くない影響が及ぶ危険性があります。
API ドキュメントへのリンクをコメントする
利用する API のドキュメントへのリンクをコメントとして残しておくことで、他の開発者やレビュアーがスムーズに確認を進めることができるようになります。チームで利用しているドキュメントツールの検索機能によっては API ドキュメント探しはなかなかしんどい作業になることがあるため、地味に助かるときがあります。
編集履歴のあるツールやサービスなどを用いて公開されている API ドキュメントであれば、実装時点でのドキュメントのリビジョンへのリンクにしておくと役立つことがあります。
リクエストとレスポンスの型を定義する
リクエストとレスポンスの型を定義することで、実装の見通しがよくなる、実装を機械的に解析できるようになるなどの恩恵を得られます。
利用する API の仕様が OpenAPI などで定義されており、CLI から型をエイヤと自動生成できるのであればそちらを利用しましょう。
そうではない API を利用する場合は、API ドキュメントなどを参照しながらリクエストとレスポンスの型を定義することになります。ただし、利用しようとしている API の質があまりよろしくなく、たとえば状況によってあるフィールドの値が変わったり、近い概念のフィールドなのになぜか型が違ったりなどする場合は、とてもつらい気持ちになることがあります。その場合は提供者に改善や新しい API の提供の検討を依頼したいところですが、厳しければこちら側で徳を積むことを覚悟するしかありません。
認証情報をベタ書きしない
API リクエストに必要なパスワードや API キー、クライアントシークレットといった認証情報は、外側から API クライアントに渡すようにしてください(具体的な方法は周辺の実装やツールチェインにもよるため省略します)。決して API クライアントの実装にベタ書きしないように注意してください。
なお、認証情報に限らず、設定値は基本的に実装から分離することが望ましいです。このあたりの方法論については、足がかりとして The Twelve-Factor App の III. 設定 を参照することをおすすめします。
エラーをハンドリングする
API サーバが返しうるエラーについて、可能であれば網羅的にハンドリングすることが理想ですが、エラーの内容によってはハンドリングしなくても問題にならない場合もあります。たとえばリクエスト前に値をチェックすることで未然に防げるエラーまでハンドリングするのは、あまり恩恵を受けられないかもしれません。実装のコストや合理性に基づいて、どのエラーをどうハンドリングするかを判断してください(とはいえ、網羅的にハンドリングすることが理想であることに違いはありません)。
また、網羅的にハンドリングしていても想定外のエラーは常に発生するものだとして、想定外のエラーが発生しても実行中の処理に影響がないように、あるいは場合によっては安全に処理を中断させるように実装したいところです。想定外のエラーはかならずログに出力し、今後そのエラーを想定内のエラーとしてハンドリングできないかを検討してください。
タイムアウト値を設定する
HTTP リクエストが完了するまでの時間は決して一定ではなく、環境や状況によって変化します。例として、API サーバの負荷が高まっているときやネットワークが不安定なときなどは、平常時よりも多くの時間を要する場合があります。
API クライアントを利用する処理がどのような利用のされ方をしているかにもよるため一概には言えませんが、HTTP リクエストの完了を待つ間、API クライアントを利用する処理がコンピュータのリソースを占有してしまい、それ以外の処理も影響を受けて全体のスループットが低下してしまうといった状況が発生することがあります。
そうした状況を防ぐためにも、リクエスト時にタイムアウト値を設定する必要があります。タイムアウト値は、もっとも負荷の高い時間帯で実際に所要する時間を基準に決定すると手堅いでしょう。
余談ですが、更新系の API へのリクエストについては別途注意が必要です。具体的には更新系の API へのリクエストがタイムアウトしたとしても、API サーバ側では処理に成功していて不整合が起きる可能性を考慮しなければなりません。もしも「タイムアウト時にはリトライする」などの実装を考えている場合は、API サーバがすでに処理済みの同じ内容のリクエストを受けたときに、409 conflict
やそれに相当するエラーをレスポンスしてくれるかどうかを API ドキュメントなどであらかじめチェックしておくべきです。
User-Agent ヘッダを指定する
User-Agent ヘッダは HTTP リクエストに必須の情報ではありませんが、API の提供者が API の利用状況の調査や集計をする場合に役立つ情報となりえます。
特に以下のような API のクライアントを実装する際には、できる限り指定することが望ましいです(API の提供者と合意が取れているのであれば、User-Agent ヘッダでなくとも利用者を識別できるデータを任意のリクエストヘッダやリクエストボディに指定するでも問題ありません)。
-
個人や有志の開発者が提供する API
提供者が連絡先を追えるようにパッケージ名やツール名などを指定することで、実装ミスなどによりお行儀の悪いリクエストをしてしまっている場合や API の仕様変更、廃止があった場合に、勧告を受けることができるかもしれません。個人や有志の開発者が提供する API は基本的に善意に基づいて運用されているため、普段以上にお行儀のよい利用を心がけたい気持ちです。 -
社内の同じチームや他チーム、協力会社などが提供する API
API の利用者を特定し、なにかあった際にはどこに問い合わせるべきかがわかるようにしましょう。すでに取り決められた値がある場合もあるので、雑に設定するのではなく、近くの人に尋ねることをおすすめします。
ログを出力する
HTTP リクエストとレスポンス、エラーの内容をログに出力しておくことで、調査や検知などに役立てることができます。特に失敗したリクエストやエラーを出力しておくと救われる場面が訪れるのではないでしょうか。
実装する際には、使用する HTTP クライアントに Interceptor や Middleware と呼ばれるしくみを準備してログ出力する方法を検討してください。処理を一箇所にまとめられることに加えて、対応するエンドポイントが増えたときや他の API クライアントを実装するときでも網羅的に対応できるといったメリットがあります。
ログに出力する内容は実際に役立つと思ったものに限定し、機密性の高い情報は絶対に出力しないように注意してください。そのようなケースはよほどのことがない限りないかと思いますが、どうしてもどうしても必要という場合は一人で判断せず、社内のセキュリティを専門にする部署や部門にかならず相談しましょう。そうした部署や部門がなく相談できない場合は、外部の企業や団体にコンサルティングを依頼するか、諦めるべきです。
実際にログを出力し始める前に、念のため 1 日あたりに出力されるログのサイズを見積もっておきましょう。fluentd などを使ってどこかに集約する場合は流量とコストが問題ないか、ホスト上にログファイルを出力する場合はストレージの空き容量とコストが問題ないかをあらかじめ確認する目的があります。ログローテートの設定も忘れないようにしましょう。
計測する
リクエストしたエンドポイント、レスポンスタイム、レスポンスのステータスコードを計測し、集計や調査ができる状況を整えることで、スローダウンや障害発生時などの原因調査に役立てることができます。
すでに AWS CloudWatch をはじめとするモニタリング基盤を利用しているのであれば StatsD などを用いてエイヤとやってしまうことをおすすめします。そうでない場合は別途ログファイルに出力し、何らかの仕組みで可視化することを検討すると良いかもしれません。
実装する際には、モニタリング基盤を利用するしないに関わらず、ログ出力と同様に HTTP クライアントの Interceptor や Middleware を準備する方法を検討してください。
監視する
ログ出力や計測を行うようにすることで、API リクエスト時のエラーやエラー率の上昇、レスポンスタイムの悪化などの異常を検知できるようになります。できれば検知した場合に通知するしくみの準備を検討してください。モニタリング基盤があれば楽ですが、ない場合は少し検討が必要です。
モニタリング基盤を利用していない場合は、シェルスクリプトなどでログファイルをチェックするような素朴なしくみを考えるのが良いかもしれません。ただし、そのようなスクリプトは沼になりやすいため、あらかじめどのように管理するかを考えておきたいところです。
おわりに
いくつかの内容は Web アプリケーションの規模や目的、組織の状況などによっては大げさ過ぎるため、実状を考慮した上で適用するかを判断すべきということをお含みおきください。