外部サービス提供のAPIを叩く時に気をつけていること
はじめに
外部サービスが提供しているWebAPIを利用して、自社サービスのシステム機能を作成する事も少なくないと思います。
サーバーサイドで外部APIを叩く経験が何回かあったので、
開発後に気づいたことなどを踏まえて設計・実装・運用上で気をつけていることを書いていきます。
自社で利用しているサービスのAPIを叩く話よりはエンドユーザの(OAuth2やOpenID Connectを利用)リソースに対して操作するケースの話を多めにします。
設計面
ドキュメントを読み込む
基本的な事だと思いますが、利用サービスが提供しているAPIのリファレンスを読み込むことは重要です。
各外部サービス毎に異なる制限事項が細かい所に記載されていたりするので、ドキュメントから探す癖をつけるのがお勧めです。
-
RateLimit
が設定されているAPIは存在するか - エンドユーザの利用プランに応じて挙動や制限が行われるAPIはないか
- モデルの制約
nullableであるか、比べて文字数や数値の境界の制限に関して自社のモデルの方が制限が厳しい、またはその逆であるか。(必要に応じてトリムしたり、自社のモデルの制限を緩和する必要がある)
基本的なインターフェースのみではなく、細かい制限事項などをドキュメントから探すようにしましょう。
実際にサービスを利用して、挙動を確かめてみる
利用サービスに詳しい人が社内にいる場合でも、サービスを利用して仕様を確認することで考慮もれを防ぐ可能性が上がります。
特定の操作をした後のプロパティの状態のパターンを確認できることや
リソースの集合に対する制約(ユニーク制約)や作成上限などの確認を実装前に手早くできるので、
実装前に軽くサービスを操作することをお勧めします。
また、ドキュメントやサービスの挙動で不明点があれば、気軽にお問合せ窓口から連絡するとリードタイムが短縮できます。
連携解除の仕様を決めておく
連携解除の機能を提供した方が、ユーザは安心してシステムを利用できると思います。
連携解除の機能を提供するかどうか、提供する場合はデータの破棄を行うかなど対応する範囲や仕様を予め決めておいた方が実装、運用時の対応工数が減ります。
実装面
Open API ツールを利用してコード生成ができるか確認する
Web APIのインターフェースを記載する OpenAPI Specification という仕様があります。
この仕様で記載されたJSON, YAMLのインターフェース定義から、API Client
の定義や,モックサーバーなどを自動で生成することができます。
叩くAPIの本数が多いと開発コストや修正コストが嵩んでしまいます。
コマンドを叩けば、リクエスト、レスポンスの生成や既存のコードの修正、型定義やバリデーションの生成なども自動で生成することができるので、開発コストが格段に下がります。
コードが自動生成可能か事前に調査した上で実装に入った方が良いです。
一方APIを公開する時はOpenAPIの仕様にのっとりドキュメントを書いて欲しいところです。
トークンの保管と運用
設計の話とも絡みますが、外部との認証に使うトークンはセキュリティが担保されているか十分な検討を行う必要があります。
利用者だけでなく、社内の開発者のアクセス権限まで配慮する必要があります。
社内のアクセス制限を行わない場合
開発者やデータベースにアクセスできる人が金銭の移動を伴うAPIや機密情報を取得するAPIを不正に利用する動機を作成することになります。
具体的には思わぬインシデントを招かないように以下のことに注意しています。
-
RFCの記載事項に則った実装を行う。可能な限りMayやShouldに記載されている項目にも従う
RFC 6749: The OAuth 2.0 Authorization Framework -
一度保存したトークンはクライアントに返さない。
保存したトークンを利用する際はサーバーを経由して呼び出すことで、トークンを扱うスコープを狭くします。
クライアントでは連携日時やアクセストークンが保存されているかどうかというフラグを返すことで認証済みかどうか
判別することができます。特にリフレッシュトークンは非公開にしましょう。 -
別テーブルで管理する
アクセストークンを保存するテーブルとユーザなどの認証単位を保管するテーブルを分けることで、
別権限で管理することができます。調査担当者はユーザレコードを確認することができるが、トークンはリーダー以上ではないと閲覧できない
という運用を取ることができます。
アクセストークンに限った話ではないですが、権限レベルの異なるものは別テーブルで管理することも検討しましょう。 -
トークンはログに残さない
機密情報はログに残さないというのは一般的な原則と思います。トークンも漏れなく、ログには残さないようにしましょう。
ログへの不正アクセス時の被害を抑える、意図せず権限が不足している人が確認できないようにするためです。
リクエスト/レスポンスのログを取る時にうっかりトークンの中身をログ出力していたというのは起こり得るので、
実装やコードレビューを行うときにしっかり確認しましょう。 -
トークンのリフレッシュ時の排他制御
- 複数のAPIを同時に叩いた場合に同時にトークンをリフレッシュすると片方のAPIがの実行が失敗してしまうことが考えられます。
- トークンの発行時にトークンの有効期限を保存しておいて、利用時に残り期限がある程度の閾値を超えたらリフレッシュするまでロックする
- またはAPIのレスポンス内容がトークンが失効していることが原因である場合にリトライの処理を入れる
などで排他制御を行うと良いでしょう。
- 複数のAPIを同時に叩いた場合に同時にトークンをリフレッシュすると片方のAPIがの実行が失敗してしまうことが考えられます。
トランザクション管理
異なるサービスとの連携は別データソースでのデータ管理とも言い換えることができます。
単一データソースのトランザクション管理よりも考慮すべき事項が多いです。
例えば、自社リソースのステータス更新後に、外部サービスのリソースを作成するなどのユースケースがあったとします。
自社ステータスの更新が失敗したのに、外部サービスのリソースが作成されている状態が発生し得ます。
- APIコール後に後続の処理が失敗しても問題ないか
- 自社リソースの更新終了後にAPIを叩くなど、処理順を工夫する
- 分散システムのトランザクション管理を取り入れる。(TCCパターンとSagaパターンでマイクロサービスのトランザクションをまとめてみた)
など例外時のリカバリー対応を設計・実装時に気をつける必要があります。
タイムアウトとリトライ処理
内部通信でももちろん考慮すべき事項ではありますが、API利用時は分散システムという特性上慎重になった方が良いです。
-
タイムアウト
外部サービスのメンテナンス時やサービス障害時などでレスポンスの遅延や停止などが発生する可能性もあります。
タイムアウトの設定とタイムアウト発生時の処理などを記載しないといつまでもレスポンス待ちで自社サーバーのパフォーマンスに影響を及ぼすことがあります。
タイムアウトを設定しないことにより具体的に以下の問題が発生すると考えられます。- 外部サービス停止
- ユーザが何かしらのアクションを実行
- レスポンスが帰ってこないので、スレッドが待機状態に
- 応答がない事を受けてユーザが画面のリロードや再実行を行う
- 2と同じ操作を繰り返す
- 待機状態のスレッドが増える
などが起こり得ます。
知っている限りではPythonのrequests
では深刻な問題が起こり得るので、使用言語の設定の確認とタイムアウトの設定を行いましょう。
【Python】requestsを使うときは必ずtimeoutを設定するべき | Cosnomi Blog
-
リトライ処理
利用サービスのサーバ状況によって5xx系のエラーが返ってくる事を時々見かけます。
例外処理をきちんと考慮している場合であってもユーザが2度同じアクションする必要があるなどUXを悪化させることもあるでしょう。
再度リクエストを行ったら成功するケースなどもあるので、適切な間隔で適切な回数リトライの処理を入れると成功するパターンもあるので、
リトライ機構を実装することは重要だと考えています。
タイムアウトとリトライ処理はAPIクライアントの共通化などで、システムワイドに定義してしまうのが楽なのかなと思います。
テストコード
ユニットテストを書くときに毎回外部リソースを叩くのは利用システムの負荷が高まる事や自動テストの実行時間が増えることなどを考えると
やらない方が良いと思います。OpenAPIで自動生成したコードやスタブ、モックの利用を行なって単体テストを記載するようにしています。
一方、実際に疎通確認をしたい時にすぐ試せるように、
環境変数やテストコンフィグの設定で切り替えて実際の外部サーバーとの通信ができるようにしておくと好ましいでしょう。
運用面
ログを取る
外部APIは利用サービスの内部仕様など見えない部分がたくさんあり、リリース後に予期せぬエラーが発生することもあるでしょう、
エラー調査に工数を取られないため、また利用状況の調査に利用するため
リクエストやレスポンスの中身を調査しやすい形でログに残しておいた方が運用上楽になります。
メンテナンス情報を定期的にWatchしておく
APIはライブラリと同じくメンテナンスされて、変更が行われていきます。
破壊的な変更に気付かずに、プロダクション環境でエラーが発生して調査や対応に工数を取られる、
お客様の信頼度が落ちるといったことが発生しないように、リリースノートは定期的に変更を確認検知する仕組みを整えていた方が良いです。
利用システムのリリースノートや利用内容をNotionやSpreadSheetなどで一覧にしておくと管理が楽になるのでおすすめです。
終わりに
単純にAPIを動かすだけなら、数時間も掛からずに実装を完了させることができます。
お手軽に、外部システムの機能を利用できるものの、実際の運用まで見越すと考慮すべき事項が多くシステムの複雑性が増したり、
リリース後のメンテナンスコストが結構掛かるという印象です。
とはいえ、外部サービスとうまく連携してサービスを特化させるという戦略は重要なので、上手くWebAPIと付き合っていきたいです。
また個人的にどでAPIを利用する機会も多く、APIので実現できる疎結合な世界が好きなので、上手く実装を行いたいと思っています。
APIを提供する側で気をつけることやWebhookなどで、外部サービスからリクエストを受けるときに気をつけることなどについても今後書いていきたいです。
sweeepでは一緒に働くエンジニアを募集しています!
分散システムのアーキテクチャに強い方一緒に働きませんか?
興味を持ってくれた方は是非↓を覗いてください
参照
外部サービスと連携するときのゆる心得 - Qiita
【Python】requestsを使うときは必ずtimeoutを設定するべき | Cosnomi Blog
APIを使った開発を行うときに気をつけるべきこと | NTT Communications Developer Portal
Discussion