0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Durable Functionsのファンアウト/ファンインパターンでのエラーハンドリング

Posted at

Durable Functions は、サーバーレス コンピューティング環境でステートフル関数を記述できる Azure Functions の拡張機能です。(以下のURLからコピペした)
https://docs.microsoft.com/ja-jp/azure/azure-functions/durable/durable-functions-overview?tabs=csharp

チュートリアルに「ファンイン/ファンアウト」というパターンがある。

これのエラーハンドリングについて調べてみた。

1. 調べたかった事

オーケストレーター関数が複数のアクティビティ関数を呼び出したとする。

このとき、特定のアクティビティ関数だけエラーが発生したらどうなるのか?

そもそもエラーハンドリングできるのか?

エラーハンドリングをできたとして、エラーが発生したアクティビティ関数は特定できるのか?

エラーが発生した場合、成功した他のアクティビティ関数の結果はどうなるのか?使えるのか、使えないのか?

このあたりを知りたくて、簡単な関数を作成して、デバッグしてみた。

1. 作成した関数の概要

HTTPトリガーで起動するオーケストレーター関数と、オーケストレーター関数により起動するアクティビティ関数。

オーケストレーター関数はアクティビティ関数を10回並列で呼び出す。

アクティビティ関数は、引数のintをそのまま呼び出し元にリターンする。

ただし、アクティビティ関数は引数が4の倍数(0を除く)の時だけ例外をスローする。

オーケストレーターのソースは以下。(クリックするとソースが表示されます)
TaskOrch.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;

namespace FunctionApp1
{
    public static class TaskOrch
    {
        [FunctionName("Function2")]
        public static async Task<int> RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            var tasks = new Task<int>[10];
            var errorMessageList = new List<string>();

            for (int index = 0; index < 10; index++)
            {
                // アクティビティ関数を並列で10回呼び出す
                tasks[index] = context.CallActivityAsync<int>("TaskActivity", index);
            }

            try
            {
                // 10個のタスクが終わるまで待つ
                await Task.WhenAll(tasks);
            }
            catch
            {
                // エラーが発生したことをキャッチする
                Console.WriteLine("Error has occured.");
            }
            // 成功の結果だけ集めてくる
            int total = tasks.Where(t => t.Status == TaskStatus.RanToCompletion).Sum(t => t.Result);
            // エラー結果だけを取得する
            var errors = tasks.Where(t => t.Status == TaskStatus.Faulted).ToList();
            foreach (var item in errors)
            {
                Console.WriteLine(item.Exception.InnerException.InnerException.Message);
            }

            return total;
        }

        [FunctionName("Function2_HttpStart")]
        public static async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestMessage req,
            [DurableClient] IDurableOrchestrationClient starter,
            ILogger log)
        {
            // Function input comes from the request content.
            string instanceId = await starter.StartNewAsync("Function2", null);

            log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

            return starter.CreateCheckStatusResponse(req, instanceId);
        }
    }
}
アクティビティのソースは以下。(クリックするとソースが表示されます)
TaskActivity.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;

namespace FunctionApp1
{
    public static class TaskActivity
    {
        [FunctionName("TaskActivity")]
        public static async Task<int> Run(
            //[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            [ActivityTrigger] int number,
            ILogger log)
        {
            log.LogInformation("TaskActivity start. index:" + number.ToString());
            log.LogInformation("Run datetime:" + DateTime.Now.ToString());
            await Task.Delay(1000);

            if ((number % 4 == 0) && (number != 0))
            {
                // 0以外の4の倍数の時に例外をスロー
                throw new Exception("number:" + number.ToString() + " is a mulriple of 4.");
            }

            return number;
        }
    }
}

2. 結果

結論から言うと、成功した結果だけを取得できたし、エラーが発生したアクティビティを特定することもできた。

オーケストレーター関数が最終的に取得した結果は「33」。4の倍数だけカウントしてないから、1~9の和である45から12(=4+8)引いてるので結果も妥当。

例外はTask.WhenAllでキャッチする。というか、こうやってソースを見ると、Azure FunctionsというよりはTaskに関する知識な気がする・・・・

3. デバッグしてわかったこと

Exceptionの取り出し方にクセがある。

アクティビティ関数の例外で投げたメッセージを取得するために、innerExceptionを2回も使う羽目になるとは。。。。

4. 最後に

便利ですね、Azure Functions。

サーバーレスコンピューティングだし、エラーハンドリングどうなのかと思ってたけど、普通にできるんですね。

まあ、できるんだろうなー、程度には思ってたけど・・・・実際目で見ないとわからないからなぁ・・・・

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?