Teams アプリから Microsoft Graph を使用するためのベスト プラクティス
はじめに
Teams アプリを開発する上で Microsoft Graph と連携することは欠かせません。Microsoft Graph は Microsoft 365 の統一されたエンドポイントであり、社内のさまざまなリソースにアクセスすることができます。たとえば以下のようなシナリオが考えられます。
- チームの同僚のプロフィール画像を表示したい。
- アプリに表示されている内容を自分のタスクとして Planner に追加したい。
- 指定したユーザーと来週のオンライン ミーティングを設定したい。
Teams アプリはチームのコラボレーションや生産性を高める目的で作成されることが多いです。そのためユーザビリティを損なうことなくどのようにして Microsoft Graph にアクセスするかは大きな課題になります。そこでこの記事では Teams アプリから Microsoft Graph を使用するためのベスト プラクティス (およびいくつかのテクニック) について考えてみたいと思います。
429 (Too Many Requests) HTTP ステータスに対応する
Microsoft Graph に限らず、API サービスはリクエストに対してスロットリングを設けることで、サービスが高負荷になることを防止しています。リクエストがしきい値を超える場合、リクエストは処理されず、429 HTTP ステータスが返されます。
Microsoft Graph におけるスロットリングのしきい値は以下の Microsoft のドキュメントに公開されています。
docs にもある通り、429 HTTP ステータスに対しては「エラーに対応する」「エラーを回避する」という 2 つの方法が必要です。これらはどちらか片方だけでいいというものではなく、どちらも実施する必要があります。
エラーに対応する
429 HTTP ステータスが発生する場合はレスポンスに Retry-After
ヘッダーが含まれるので、ヘッダーで指定された秒数だけ待機して、リトライをするように設計する必要があります。とはいえリトライを自前で実装するのは大変なので、Microsoft Graph SDK を使うことを検討してください。Microsoft Graph SDK には RetryHandler
というミドルウェアが存在し、429 HTTP ステータスが発生したときのリトライを代わりに行ってくれます。
エラーを回避する
バッチ リクエストについて
バッチ リクエストは Microsoft Graph へのリクエストそのものを減らしてパフォーマンスを向上させるテクニックですが、それぞれのリクエストが個別に評価されるため、429 HTTP ステータスの回避には役立ちません。また、リクエストの一部が 429 HTTP ステータスになったとしても、全体としては 200 HTTP ステータスを返すことがあるため、Microsoft Graph SDK はリトライを行いません。
レスポンスをキャッシュする
Teams アプリはタブが表示されるために新しいページが読み込みなおされます。ユーザーがタブを切り替えることは頻繁にあるため、そのたびに Microsoft Graph リクエストを送っていてはすぐにスロットリングのしきい値に達してしまいます。取得したリクエストは一定期間キャッシュすることで余計なリクエストを減らすほかパフォーマンスの向上にもつながります。キャッシュの場所としては IndexedDB を使うのがいいでしょう。また Microsoft Graph Toolkit を使用すると自動的にキャッシュも行ってくれます。
トークンの有効期間に対応する
Microsoft Graph へのリクエストには OAuth によるアクセス トークンが必要になります。アクセス トークンの有効期間はアプリケーションの種類や組織のポリシーによって期間が異なり、Microsoft のドキュメントには明示されていないものの、通常は 1 時間から数時間くらいです。[1]
Microsoft Teams は仕事をしている間はずっと起動しているため、Teams アプリも長時間起動し続けている可能性があります。アプリの使用中によってアクセス トークンの有効期間が切れるシナリオは想定しておく必要があります。
アクセス トークンの期間切れを検知する
Microsoft Graph SDK や Microsoft Graph Toolkit を使用している場合、アクセス トークンを渡すロジックはコールバックになっています。よってアクセス トークンが要求されるタイミングは開発者が制御することができます。このタイミングでアクセス トークンの有効期間が切れているかどうかをチェックする必要があります。Azure AD のアクセス トークンは JWT なのでデコードして中身を検証することができます。exp
には UNIX 時間 (1970/01/01 からの経過秒数) が含まれるため、これが過去の時間を示すときには適切なハンドリングを行う必要があります。
アクセス トークンの期間切れに対応する
手動で再表示するようにユーザーに促す
あまりスマートな方法でありませんが、一時的な回避方法として、エラー画面を表示して再表示させるというのが簡単です。アクセス トークンはアプリの初期化時に取得することが多いので、再表示をさせることで問題を解決できます。
自動的にアクセス トークンを再取得する
可能な限りこの方法を採用します。アクセス トークンが期限切れになったことを検知したとき、microsoftTeams.authentication.getAuthToken
メソッドを呼び出して新しいクライアント トークンを取得し、サーバー トークンに変換します。getAuthToken
メソッドはコールバックを受け取るのですが取り回しが悪いので Promise に変換するようにヘルパーを作ることをおすすめします。
function getAuthToken() {
return new Promise((resolve, reject) => {
microsoftTeams.authentication.getAuthToken({
successCallback: (token) => resolve(token),
failureCallback: (error) => reject(error)
});
});
}
パフォーマンスを向上させる
Google Chrome では同時に接続できるリクエストの数が 6 に制限されています。Electron で開発されている Microsoft Teams も同様の制限を受けると考えていいでしょう。[2] よって大量のリクエストを発生させる可能性がある場合はリクエスト数を減らすことでパフォーマンスを向上させることができます。すでに述べた 429 HTTP ステータスへの対応と重複する点もあります。
OData クエリを活用する
たとえば、元となるデータとそれに関連するデータを取得するときに、$expand
パラメーターを使用することで、リクエストの回数を削減することができます。また、$select
パラメーターを使用することで、レスポンス データのサイズを削減し、パフォーマンスを向上させることができます。
バッチ リクエストを使用する
API の種類によっては $expand
ができないものもあります。たとえば、/teams/{id}
と /teams/{id}/members
はアクセス許可が異なるため、$expand
が使用できません。このような場合に、バッチ リクエストを使用することにより、リクエストを減らすことができます。注意点として、バッチ リクエストでは依存関係のあるリクエストの順序を指定することはできますが、実行した結果を別の API のリクエストに含めることはできません。
レスポンスをキャッシュする
データの種類ごとにキャッシュ戦略を立てる必要があります。プロフィール画像のような更新頻度の低いデータと、プレゼンス情報のような更新頻度の高いデータでは、当然キャッシュの有無や有効期間も異なります。
見た目を整える
リクエスト中はユーザーにそうであることを表現する必要があります。Fluent UI には Loader や Skeleton のようなロード中であることを表現するコンポーネントが用意されています。最近は Loader よりも Skeleton が好まれる傾向にあります。いずれにしても、ユーザー体験を損なうことのないように考慮するべきです。
おわりに
Teams アプリに限定して書きましたが、だいたいのことは Teams アプリでなくても当てはまりますし、さらにいえば Microsoft Graph に関係なく API を呼び出すアプリケーション全般に当てはまることもあるかと思います。
- リトライを実装する
- リクエストの回数を減らす
- データをキャッシュする
- ユーザー体験を損なわない
このあたりを留意していただければ価値のある Teams アプリを作ることができるのではないかと思います。
Discussion