Azure Durable FunctionsのHuman interaction and timeoutsを試してみたので記載していきます。
試した内容はDurable Fuctionsの公式にサイトにあるSamplesとWalkthrouthsの内容です。
https://azure.github.io/azure-functions-durable-extension/articles/samples/phone-verification.htmlazure.github.io
Azure Durable Functionsのローカル実行環境を構築していない人は下記を参照してください。
本記事はローカル実行環境が構築済みであることを前提とします。
https://cdn.kokoni.jp/2017/07/24/azure-durable-functions%E3%82%92%E5%BC%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F%EF%BC%88%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E7%B7%A8%EF%BC%86fcuntion-chaining%E5%AE%9F%E8%A1%8C%E7%B7%A8%EF%BC%89/cdn.kokoni.jp
また、今回のTwiilioのAPI連携を利用するためTwillowの登録をしておく必要があります。
その他のDurable Functionsのパターンについては下記を参照してください。
https://cdn.kokoni.jp/2017/07/29/azure-durable-functions%E3%81%AE%E3%80%8Clightweight-actor%E3%80%8D%E3%82%92%E8%A9%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F/cdn.kokoni.jp
https://cdn.kokoni.jp/2017/07/28/azure-durable-functions%E3%81%AE%E3%80%8Cfan-infan-out%E3%80%8D%E3%82%92%E8%A9%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F/cdn.kokoni.jp
https://cdn.kokoni.jp/2017/07/25/azure-durable-functions%E3%81%AE%E3%80%8Cfunction-chaining%E3%80%8D%E3%82%92%E8%A9%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F/cdn.kokoni.jp
※本記事は2017年7月30日時点の情報となります。
※現時点ではWindosOSでのみ対応しているのでWindows10で実施した内容となります。
Human interaction and timeouts
Human interaction and timeoutsは人間の所作を介在させるオーケストレーションを利用するパターンです。実際の人物がプロセスに介在し、その人物に通知を送信し、その人物のアクションを非同期的に応答を受け取り実施します。
今回のサンプルソースはTwilioを利用した電話認証のサンプルになります。Twilioの電話認証APIにリクエストして認証コードを取得しレスポンスを返します。Twilioの有料版であれば電話番号にSMSで認証コードを返却するということが可能ですがここではテスト用のサブスクリプションで実行するのでコンソールとポストマンでやり取りします。
まずサンプルを実行する前にTwilioの設定をしておく必要があります。Twilioのサイトにアクセスして認証キーを取得しlocal.settings.jsonに登録しましょう。
Twilioの設定>一般からテストクレデンシャルの情報を取得。
local.settings.jsonの下記の部分に取得したキーを転記。PhoneNumberはテストクレデンシャル用の"+15005550006"を利用。
"TwilioAccountSid": "************", "TwilioAuthToken": "************", "TwilioPhoneNumber": "+15005550006"
Twilioのテストクレデンシャル用の電話番号については下記のリファレンスを参照してください。
それではVSで実行後にポストマンでポストを試してみましょう。今回のリクエストURLは下記でリクエスト設定としてHeadersにContent-Type:Application/jsonを指定してBodyをRawに指定して"+15005550006"を設定してポストします。
http://localhost:7071/orchestrators/E4_SmsPhoneVerification
正常に実行されれば下記のようにレスポンスが返ってきます。ここではstatusQueryGetUriとsendEventPostUriを利用します。
現在の状態を確認するためstatusQueryGetUriを実行します。inputに指定した電話番号が表示されステータスがRunning状態になっているのがわわかると思います。
次に認証コードを取得します。こちらはE4_SmsPhoneVerificationにポスとした時のログとしてコンソールに出力されているのでそれを利用します。
認証コードをPOSTして認証するにはsendEventPostUriのURLを利用します。 {eventName}の部分を"SmsChallengeResponse"に置き換えて利用します。HttpHeadersはContent-Type:Application/jsonに指定してbodyはrawを選択して認証コードを設定しPOSTを試してみましょう。
認証コードのPOST後に現在の状態を取得するためstatusQueryGetUriのURLを実行します。下記のように状態がCompletedとなっていてoutputにtrueが設定されていれば認証が成功したことになります。
試しに間違った認証コードでPOSTを行ってみましょう。
間違った認証コードでPOSTするとoutputがfalseになっている事を確認できると思います。これは認証に失敗したことを表します。プログラムの設定では90秒のTimeOutが設定されているので認証コードをポストせずに90秒が経過しても同様に認証失敗となります。
動きを確認できたらプログラムを確認してみましょう。サンプルソースにあるPhoneVerification.csがHuman interaction and timeoutsのサンプルになります。
[FunctionName("E4_SmsPhoneVerification")] public static async TasKRun( [OrchestrationTrigger] DurableOrchestrationContext context) { string phoneNumber = context.GetInput<string>(); if (string.IsNullOrEmpty(phoneNumber)) { throw new ArgumentNullException( nameof(phoneNumber), "A phone number input is required."); } int challengeCode = await context.CallFunctionAsync<int>( "E4_SendSmsChallenge", phoneNumber); using (var timeoutCts = new CancellationTokenSource()) { // The user has 90 seconds to respond with the code they received in the SMS message. DateTime expiration = context.CurrentUtcDateTime.AddSeconds(90); Task timeoutTask = context.CreateTimer(expiration, timeoutCts.Token); bool authorized = false; for (int retryCount = 0; retryCount <= 3; retryCount++) { Task<int> challengeResponseTask = context.WaitForExternalEvent<int>("SmsChallengeResponse"); Task winner = await Task.WhenAny(challengeResponseTask, timeoutTask); if (winner == challengeResponseTask) { // We got back a response! Compare it to the challenge code. if (challengeResponseTask.Result == challengeCode) { authorized = true; break; } } else { // Timeout expired break; } } if (!timeoutTask.IsCompleted) { // All pending timers must be complete or canceled before the function exits. timeoutCts.Cancel(); } return authorized; } } [FunctionName("E4_SendSmsChallenge")] public static int SendSmsChallenge( [ActivityTrigger] DurableActivityContext sendChallengeContext, TraceWriter log, [TwilioSms(AccountSidSetting = "TwilioAccountSid", AuthTokenSetting = "TwilioAuthToken", From = "%TwilioPhoneNumber%")] out SMSMessage message) { string phoneNumber = sendChallengeContext.GetInput<string>(); // Get a random number generator with a random seed (not time-based) var rand = new Random(Guid.NewGuid().GetHashCode()); int challengeCode = rand.Next(10000); log.Info($"Sending verification code {challengeCode} to {phoneNumber}."); message = new SMSMessage(); message.To = phoneNumber; message.Body = $"Your verification code is {challengeCode:0000}"; return challengeCode; }
まずE4_SmsPhoneVerificationにリクエストをするとリクエストBodyから電話番号を取得します。その電話番号を以ってE4_SendSmsChallengeという関数が呼ばれます。E4_SendSmsChallengeは認証コードを生成して、TwilioのAPIに指定の電話番号の端末に認証コードをSMSで送付するという処理を行います。ここではテスト用の電話番号を利用しているため実際の携帯デバイスには送付されません。
認証コードのリクエストを待機するためにCancellationTokenSourceを利用してWaitをかけます。CancellationTokenSourceを利用しているので指定した秒数を越えた場合、imeout expiredとなります。プログラムでは90秒待機する設定でTask.WhenAnyが呼ばれていますので90秒を超えてリクエストがなかった場合は Timeoutとして処理を続行します。
Human interactionは人間の所作を介在させるパターンなので人間の所作が完了するまで処理を待機することは理解できたと思います。そこに時間制限を加えることで所作がなくても処理を完了させることが可能なことも併せて理解できたと思います。サンプルではSMS利用したコード認証でしたが、これを利用すればLineBotで時間制限付のクイズゲームなんかが作れちゃうんじゃないかと愚考する次第です。もう少しいいアイデアがわかないものかしら・・・。