Bot Framework と Microsoft Graph で DevOps その 20 : ステートサービスで状態の管理
今回は状態管理について。既にアプリのコードで多用してますけど、あらためて説明を。
概要
ボットに欠かせない重要な機能が状態管理。例えば、前回ユーザーが何を言ったかという単純な事から、ユーザーや会話のコンテキストを理解するため等に使います。Bot Framework はステートサービスと呼ばれる状態管理の仕組みを提供しており、ダイアログやフォームフローは、裏でこの仕組みを使ってユーザーとの会話を管理しています。
スコープ
Bot Framework は以下 3 つのスコープで状態管理が行えます。
- ユーザー単位 (UserData) : 会話に関わらずユーザー個人としてのステート。
- 会話単位 (ConversationData) : 現在の会話に関するステート。会話は対個人も対グループもある。
- 会話の中の個人単位 (PrivateConversationData) : 現在の会話かつ、特定個人に関するステート。
どのスコープにデータを保存するかは、データの性質によって変えます。
ステートサービスのデータ保存場所
既定でマイクロソフトが管理するサーバーに保存しますが、独自に管理したい場合、任意の Azure Table Storage か DocumentDB に変更が可能です。サンプルが GitHub に公開されているので参考にしてください。データの保存地域やスペックも独自に決められるため、パフォーマンス向上にもなります。
尚、以下主な注意点。
- ステートはチャネル単位。実質同一ユーザーでもチャネルが異なるとステートは別と扱われる。
- 保存するデータはシリアライズ可能なもの。
- 保存できるデータサイズは 64 KB。情報元はこちら。マスターデータ的なものは別途他の DB から取得し、ボットアプリに関連する情報のみステートサービスに保存。
- ステートサービスはボットアプリを公開するサーバーとは異なる場所にデータを保存するため、サービスを再公開してもデータは消えない。一方エミュレータを使っている場合、ステート情報はエミュレータ内に保持。よってエミュレータを再起動するとデータが消える。
ステートクライアント
ステートサービスに接続するためのクライアントは、以下の方法で作成、取得できます。
新規に作成
StateClient stateClient = new StateClient(new MicrosoftAppCredentials(microsoftAppId, microsoftAppPassword));
Activity から作成
StateClient stateClient = activity.GetStateClient();
ダイアログの場合は、IDialogContext に既にステートが含まれます。
ステートクライアントの使い方詳細はこちら。
同時実行制御
同時に複数のスレッドからステートを保存し競合が発生すると、HTTP ステータス 412 エラーが出ます。以下のようにハンドルしてください。
try
{
// ユーザーデータの取得
BotData userData = await stateClient.BotState.GetUserDataAsync(activity.ChannelId, activity.From.Id);
// データの変更
userData.SetProperty<bool>("SentGreeting", true);
// データを保存
await stateClient.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
}
catch (HttpOperationException err)
{
// HTTP status 412 Precondition Failed エラーをハンドル
}
ボットアプリでの活用場所
ダイアログ
ダイアログおよびフォームフローは、ダイアログ全体をシリアライズして、ステートを管理しています。明示的な保存や取得は必要ありません。デバッグ中に PrivateConversationData の DialogState が実体です。
Office 365 Webhook の購読 ID プロアクティブ通知の回で実装した Office 365 Webhook の情報を、ユーザー単位のステートに格納しています。
// 変更を購読
var subscriptionId = context.UserData.GetValueOrDefault<string>("SubscriptionId", "");
if (string.IsNullOrEmpty(subscriptionId))
{
subscriptionId = await service.SubscribeEventChange();
context.UserData.SetValue("SubscriptionId", subscriptionId);
}
AuthBot
AuthBot も認証情報をステートサービスに格納しています。GitHub のコードを確認してください。
ユニットテスト
既に利用しているユニットテストの仕組みでは、ステートサービスもモックされています。ただしチャネルなどを個別に明記した方法でデータの管理をしている場合は考慮が必要です。
まとめ
個人的には、会話の進捗状況管理が一番面倒だと思います。BotBuilder のダイアログはそこを自動でカバーしているのが最高です。他に保存すべきステートがあればステートサービスを活用してください。次回は音声対応について紹介します。