Azure Functions の 超イケてる Durable Functions を使ってみる

Azure のサーバーレスサービスである Azure Functions で、[Durable Functions](https://azure.github.io/azure-functions-durable-extension/index.html)という新しい機能が公開されました。もちろんまだ GA ではありませんが、自分的には相当熱い機能です。これを教えてくれた Kanio と、これあったら、もうサーバーイランのとちゃうん！と盛り上がりました。これもいつも通り、自分のためのメモとしてかいていきます。

# 1. 全体像

Durable Fucntions は Azure の extension で、ロングランニングで、ステートフルなFunction のオーケストレーターです。これだけではよくわからないと思うので、ユースケースを見てみましょう。

今回は公開されたというレベルなので、発展途上ですが、既にこんな機能が実装されています。具体的な実装例が、[ここ](https://azure.github.io/azure-functions-durable-extension/articles/overview.html)で見れます。![actor.png](https://qiita-image-store.s3.amazonaws.com/0/3470/c73af30c-e23d-dfbd-3df8-0b683f3f37a6.png)


## 1.1. Function Chaining

Function から Function を簡単に呼べる Function Chaining パターン

![function-chaining.png](https://qiita-image-store.s3.amazonaws.com/0/3470/9cf77599-914f-e409-ec6a-6021eccb738f.png)

## 1.2. Fan-out, Fan-in

並列実行して、それが全部終わったら、次のファンクションに流す。非同期実行を、最後で待ち合わせてくれるのがみそ。

![fan-out-fan-in.png](https://qiita-image-store.s3.amazonaws.com/0/3470/4242815b-71e1-8559-5ea7-bbb9b213d075.png)

## 1.3. Async HTTP APIs

これはロングランニングの非同期 HTTP API の実行で役にたつパターン。ロングランニングの非同期実行のfunction のステータスを問い合わせられるAPIが作られる。

![async-http-api.png](https://qiita-image-store.s3.amazonaws.com/0/3470/ea8cee89-f969-8942-0f3a-400df9b9128e.png)

## 1.4. Lightweight Actors

これが自分的には熱いのですが、Service Fabric でもおなじみのActor を使える。つまりStateful を扱えます。 Service Fabric の実装とは異なってライトウェイトでシンプルな感じです。

## 1.5. Human interaction and timeouts

人が介入して承認とかするようなパターンです

![approval.png](https://qiita-image-store.s3.amazonaws.com/0/3470/c4c5395c-8aa3-63fe-0dde-65c04a067020.png)

# 2. プログラムと実行

手始めに、自分で複数のfunction を並列実行させて、その戻り値を返すようなFunction を書いて、ローカル、Azure の両方で動かしてみたので、シェアしたい。

## 2.1. インストール

[Install Durable Functions](https://azure.github.io/azure-functions-durable-extension/articles/installation.html)の指示通り、実施すればよい。

現在のところ、C# のみであるが、Visual Studio 2017 Preview (2) がDurable Function サポートしている。大まかにいうと、

**Visual Studio**

* Visual Studio 2017 Preview (2) をインストールする
* DurableFunctionsBinding.zip というDLL集を、C:\BindingExtensions に展開する。
* local.setting.json というファイルがあるので、そこにBindingExtensionsの場所を記述する
* NuGet パッケージで、DurableTask というパッケージをインストールする
* .csproj ファイルに`<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="0.1.0-alpha" />`を追加
* ローカルに Azure Storage Emulator を入れて起動しておく。

という感じだ。ちなみに、local.setting.jsonのサンプルをあげておくとこんな感じ。Azure Functions はローカルで起動してデバッグできるが、その設定。

```
{
    "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://127.0.0.1:10002/",
    "AzureWebJobsDashboard": "UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://127.0.0.1:10002/",
    "WEBSITE_HOSTNAME": "localhost:7071",
    "AzureWebJobs_ExtensionsPath": "C:\\BindingExtensions"
  }
}
```

**Azure** 

ちなみに、Visual Studio がなくても、Azure だけあれば、実施できる。その場合は下記の感じ。これも同じく先のページの手順でいける。

* DurableFunctionsBinding.zip を、kudu で D:/home/BindingExtensionsに展開する。
* Functions の Application Settings > App Settings に、`AzureWebJobs_ExtensionsPath`と`D:\home\BindingExtensions` を登録する。

これで、基本的な設定は終了だ。ちなみにサンプルソースは、[ここ](https://github.com/TsuyoshiUshio/DurableFuctionSample)に置いておいた。

## 2.2 スタートアップ Function 

最初に、最初のリクエストを受け付けて、該当する Functions を実行する Function を作成する。このコードはほぼ定型になるだろう。

```Csharp
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;

namespace DFSample
{
    public static class ControllerFunction
    {
        [FunctionName("ControllerFunction")]
        public static async Task<object> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, methods: "post", Route = "controller/{functionName}") ] HttpRequestMessage req,
            [OrchestrationClient] DurableOrchestrationClient starter,
            string functionName,
            TraceWriter log)
        {
            log.Info($"ControllerFunction controller/{functionName} was triggered!");

            dynamic eventData = await req.Content.ReadAsAsync<object>();
            string instanceId = await starter.StartNewAsync(functionName, eventData);
            log.Info($"Started controller with ID = '{instanceId}'");

            return starter.CreateCheckStatusResponse(req, instanceId);


        }
    }
}
```

ポイントは、アノテーションで、ファンクション名を指定しているところと、`OrchestrationClient`というアノテーションで、DurableOrchestrationClient をRun の引数にとっているところだ。　ここにわたってきた、functionName をもとに次のファンクションが実行される。

今回の構造は、

```
ControllerFunction -> ParallelExec -> EchoExec
```

というシンプルな構成で、ParallelExec が EchoExec を３並列で走らせて、結果が戻って着しだい、呼び出し元に対して結果を返すというものだ。先のパターンでいうと。`1.2.`に該当する。

次に ParallelExec 

```Csharp
using System.Linq;
using System.Net;
using System.Net.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.WindowsAzure.Storage.Table;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace DFSample
{
    public static class ParallelExec
    {
        [FunctionName("ParallelExec")]
        public static async Task<List<string>>  Run(
            [OrchestrationTrigger] DurableOrchestrationContext context, // Point 1
            TraceWriter log
        )
        {
            log.Info("ParallelExec started!");
            var tasks = new Task<string>[3];
            tasks[0] = context.CallFunctionAsync<string>(  // Point 2
                "EchoExec",
                "Hello, I'm Tsuyoshi"
                );
            tasks[1] = context.CallFunctionAsync<string>(
                "EchoExec",
                "Hello, I'm Kanio");
            tasks[2] = context.CallFunctionAsync<string>(
                "EchoExec",
                "Hello, I'm NEO");
            await Task.WhenAll(tasks);                     // Point 3
            var outputs = new List<string>();
            foreach(Task<string> task in tasks)
            {
                outputs.Add(task.Result);
            }

            log.Info("ParallelExec Done!");
            return outputs;
        }

        [FunctionName("EchoExec")]                       // Point 4
        public static string EchoExec (
            [ActivityTrigger] DurableActivityContext context, 
            TraceWriter log)
        {
            
            string message = context.GetInput<string>();
            log.Info($"EchoExec started with '{message}'");

            return message + "\nHi, I'm Azure Fucntions";
        }
    }
}
```

Point 1 で、OrchestrationTriggerのアノテーションを指定して、Context を取得している。Point 2 context.CallFunctionAsync<T> で、別のファンクションを実行している。ここでは、EchoExec である。そこに、引数を渡して、非同期実行している。awaitキーワードは非同期実行の待ち合わせをして、同期処理のようにかけるが、スレッドはブロックしないため並列処理の効率が良い。 Point 3 Task.Wait(tasks) によって、３つの非同期処理が、３つとも３並列で動いているが、それがすべて終わるまで await で待ち合わせている。
最後に、 Point 4 で、さらに次の Function の実行部のロジックが記述されて、いる。

これを実行してみよう。

## 2.3 実行

F5 キーでローカルに Function が起動する。起動しおわったら、Postman などでアクセス可能になる。


![001.png](https://qiita-image-store.s3.amazonaws.com/0/3470/75e9fc13-890c-3e88-b360-d6deacdf6933.png)

このコンソールの一番最後にアクセス先が出てくるので、リクエストをポストする。おそらく`http://localhost:7071/controller/ParallelExec` になる。

![002.png](https://qiita-image-store.s3.amazonaws.com/0/3470/162f936d-66d1-68f4-d9c4-429598b5cf79.png)

動いた！この赤く囲ったURLをクリックしてみよう。これが結果を見るURLだ。

![003.png](https://qiita-image-store.s3.amazonaws.com/0/3470/c2e63e37-c8c6-cdd3-5faf-e14fece77a33.png)

しっかり予想通りの結果になっている。

## 2.4. Azure への Push

Visual Studio のプロジェクトここでは DFSample を右クリックすると、Publish が選択できる。ウィザードに従えば、Azure Functions がサーバーに送信されて動くようになる。

![004.png](https://qiita-image-store.s3.amazonaws.com/0/3470/89f6a1b3-5125-cbff-ec9f-b18c6115b4a3.png)

先ほど書いたファイルがそれぞれUpload されている。ポイントは、ControllerFunction の1をクリックすると、このFunction にアクセスするURL が得られる。 2 もポイントである通常のAzure Functions だと、run.csx と、 function.json が少なくともあるのだが、ここにはない。function.json を見ると、最後にScriptFile で、dllを指定しているので、クライアントからは、dll 形式になってアップロードされていると思われる。

実行してみるともちろん結果は同じ

![005.png](https://qiita-image-store.s3.amazonaws.com/0/3470/ec9d8f37-ab27-aeb2-c65a-043474ae0c6e.png)

## 2.5. Azure のみで Durable Functions を作るケースの注意

さて、わたしは、Durable Functions を、Azure の Portal のみでも作ってみた。そのケースは、自分で、function.json を書く必要がある。例えばこんな感じ。将来はテンプレートが選択可能になるだろう。

function.json

```json
{
  "bindings": [
    {
      "name": "context",
      "type": "orchestrationTrigger",
      "direction": "in"
    }
  ],
  "disabled": false
}
```

run.csx

```
#r "Microsoft.Azure.WebJobs.Extensions.DurableTask"

public static async Task<List<string>> Run(DurableOrchestrationContext context)
{
    var outputs = new List<string>();

    outputs.Add(await context.CallFunctionAsync<string>("E1_SayHello", "Tokyo"));
    outputs.Add(await context.CallFunctionAsync<string>("E1_SayHello", "Seattle"));
    outputs.Add(await context.CallFunctionAsync<string>("E1_SayHello", "London"));

    // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
    return outputs;
}
```

`#r` の指定、そして、アノテーションが無くなっていることに注意されたい。実はAzure Function を普通に書くケースはこのような感じで、クラスの形式になっていない。先のクライアントのコードは、Class でアノテーションがある。こちらのコードが先のような形式に変換されて実際はDLLに格納されるのかもしれない。

ちなみに、Azure Portal 用のコードは、[Gist](https://gist.github.com/TsuyoshiUshio/365e5ce453d02a3f707635feeb513c8d)に置いておいたのでよかったらどうぞ。

# 3. おわりに

Azure Functions の Durable Functions は自分的には相当可能性を感じる。相当な複雑な並列実行ワークフローも簡単に書けるし、シンプル。しかも、ロングランニングなものの考慮、アクターの実行までできる。ほんまこれがあれば、サーバーの御守が不要になる未来が近づいているのを感じる。今後も継続的に機能を試していきたい。

# 4. リソース

* [Durable Functions](https://azure.github.io/azure-functions-durable-extension/index.html)
* [Visual Studio のサンプル](https://github.com/TsuyoshiUshio/DurableFuctionSample)
* [Azure Portal のサンプル](https://gist.github.com/TsuyoshiUshio/365e5ce453d02a3f707635feeb513c8d)