Durable Functions Getting Started - C# 

Visual Studio を使って初めての Durable Functions を書いてみましょう。

# 前提条件 

下記のリンクに、Visual Studio を用いて開発を行うための前提条件が書かれています。内容がたまにアップデートされるので、ここで詳細は書かず、リンクにとどめておきます。丁寧にかいてありますので、この前提条件に従うと問題ないとおもいます。

* [Visual Studio を使用して初めての関数を作成する - 前提条件](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-your-first-function-visual-studio#prerequisites)

ポイントだけを説明しておきますと

* Visual Studio 2017 のインストール
* Visual Studio installer で [Azure 開発がインストール](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-develop-vs#prerequisites)されている。
* [Azure Functions WebJobs tools のインストールとツールの更新](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-develop-vs#check-your-tools-version)
 
の三点がそろっていれば大丈夫かと思います。

# Azure Functions (V2) プロジェクトを作成する

Azure Functions は、V1 と V2 が存在します。詳しくは[Azure Functions ランタイム バージョンの概要](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-versions)をご参照ください。今後は V2 の使用をお勧めしますので、この手順は V2 について書かせていただきます。

a. Visual Studio で、[ファイル] メニューから [新規] > [プロジェクト] の順に選択します。
b. [新しいプロジェクト] ダイアログで、[インストール済み] を選択し、[Visual C#] > [クラウド] の順に展開して [Azure Functions] を選択します。プロジェクトの名前を入力して、[OK] をクリックします。 関数アプリ名は、C# 名前空間として有効である必要があります。そのため、アンダースコア、ハイフン、その他の英数字以外の文字は使用しないでください。
   
![AzureFunctions01.JPG](https://qiita-image-store.s3.amazonaws.com/0/3470/6989a39c-c802-d9a2-da4e-ad9fee7479a2.jpeg)
c. 図の下の表に示した設定を使用してください。
![AzureFunctions02.JPG](https://qiita-image-store.s3.amazonaws.com/0/3470/32fa942c-997f-6d51-2c7a-f5c16304cd80.jpeg)  

| Settings | 推奨値 | 説明 |
|----------------- | ------------------- | ---------------------------- |
| バージョン | Azure Functions 2.x (.NET Core)| これは、.NET Core をサポートする Azure Functions のバージョン 2.x ランタイムを使用する関数プロジェクトを作成します。 Azure Functions 1.x では、.NET Framework がサポートされます。 詳細については、「[Azure Functions ランタイム バージョンをターゲットにする方法](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-versions)」をご覧ください。|
| テンプレート | Empty | Azure Functions 2.x では現在のところテンプレートがサポートされていません。|
| ストレージ アカウント | Storage Emulator (ストレージ エミュレーター) | Durable Functions は、ストレージアカウントを使用します。Azure 開発をインストールすると自動的にインストールされる [Azure Storage エミュレータ](https://docs.microsoft.com/ja-jp/azure/storage/common/storage-use-emulator)をローカル開発では使用します。|
d. [OK] をクリックして、関数プロジェクトを作成します。

# Durable Functions を作成する

a. ソリューション エクスプローラーで、プロジェクト ノードを右クリックし、[追加] > [新しいアイテム] の順に選択します。 [Azure 関数] を選択します。次のような画面がでてきますので、`Durable Functions Orchestration` を選択して、[追加] を選択します。Durable Functions のテンプレートが作成され、`Microsoft.Azure.WebJobs.Extensions.DurableTask` nuget パッケージがインストールされます。

![AzureFunctions03.JPG](https://qiita-image-store.s3.amazonaws.com/0/3470/239adfc6-b1f0-6a3e-8e70-60cbd4caf428.jpeg)

## nuget パッケージの更新

デフォルトでは、nuget パッケージのバージョンが古くなっています。ソリューション エクスプローラーで、プロジェクト ノードを右クリックし、[Nuget パッケージの管理] を選択します。

![AzureFunctions04.JPG](https://qiita-image-store.s3.amazonaws.com/0/3470/394c2a0c-88f4-caab-ab5c-fb1ddbf763e3.jpeg)


[更新] タブに数字がでている場合は、[更新]タブをクリックします。インストール済みの nuget パッケージのうち更新が必要なものが表示されます。この例では、`Microsoft.NET.Sdk.Functions` が古いバージョンになっていますので、右のパネルの [更新] をクリックしてバージョンを更新します。

Durable Functions のコードができたので実行することができます。

# Durable Functions をローカルでテストする

Azure Functions Core Tools](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local)をつかうとローカルの開発用コンピューター上で Azure Functions プロジェクトを実行できます。 Visual Studio から初めて関数を開始すると、これらのツールをインストールするよう求めるメッセージが表示されます。
関数をテストするには、F5 キーを押します。 メッセージが表示されたら、Visual Studio からの要求に同意し、Azure Functions Core (CLI) ツールをダウンロードしてインストールします。 また、ツールで HTTP 要求を処理できるようにファイアウォールの例外を有効にすることが必要になる場合もあります。
Azure Functions のランタイムの出力から、関数の URL をコピーします。

![AzureFunctions5.JPG](https://qiita-image-store.s3.amazonaws.com/0/3470/c8ce7bf0-a8d7-dbe5-f496-622c04b2dc3f.jpeg)

Durable Functions が起動し、リクエストを受け付ける準備がなされました。

# コードを理解する

Durable Functions のオーケストレーションを開始します。生成されたコードを読んでみます。

## Http トリガと Orchestration Client

この関数は、Httpリクエストを受け付ける `HttpTrigger` とともに、`DurableOrchestrationClient` のインスタンスを受け取っています。この `DurableOrchestrationClient` の `StartNewAsync("関数名", null)` メソッドを呼び出すことで、オーケストレーションが開始されます。オーケストレーションの`instanceId`が自動的に生成され、FunctionName アトリビュート`HelloWorld` の関数が開始されます。

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

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

            return starter.CreateCheckStatusResponse(req, instanceId);
        }
```
## オーケストレータ

`StartNewAsync` の開始をうけて、オーケストレーションが開始されます。`DurableOrchestrationContext` のインスタンスが引き渡されます。`context` オブジェクトには、先行する関数から引き渡された引数(この例では、null)等が格納されています。`context.CallActivityAsync<T>("Activity 関数名", "引数")` を呼び出すことで、本オーケストレータ関数から、アクティビティ関数を呼び出して、処理を実行させることができます。引数の型を`<T>` で指定する必要があります。本実行は一件普通のメソッド呼び出しに見えますが、実際は、Queue を介してメッセージ送信が行われています。オーケストレータ関数には、`[OrchestrationTrigger]` アトリビュートが必要になります。本関数の戻り値がオーケストレーションの結果として引き渡されます。

```csharp
        [FunctionName("HelloWorld")]
        public static async Task<List<string>> RunOrchestrator(
            [OrchestrationTrigger] DurableOrchestrationContext context)
        {
            var outputs = new List<string>();

            // Replace "hello" with the name of your Durable Activity Function.
            outputs.Add(await context.CallActivityAsync<string>("HelloWorld_Hello", "Tokyo"));
            outputs.Add(await context.CallActivityAsync<string>("HelloWorld_Hello", "Seattle"));
            outputs.Add(await context.CallActivityAsync<string>("HelloWorld_Hello", "London"));

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

## アクティビティ

オーケストレータから呼び出された 関数です。Azure Functions のバインディング も使用することができます。本関数がオーケストレータの`CallActivityAsync<T>()` から呼び出されて実行された際のパラメータが、`[ActivityTrigger]` アトリビュートで引き渡される引数になります。戻り値は`CallActivityAsync<T>()`関数の戻り値になります。

```csharp
        [FunctionName("HelloWorld_Hello")]
        public static string SayHello([ActivityTrigger] string name, ILogger log)
        {
            log.LogInformation($"Saying hello to {name}.");
            return $"Hello {name}!";
        }
```

# オーケストレーションを開始する

Durable Functions をローカルでテストする で Durable Fucntions が起動しています。[Postman](https://www.getpostman.com/)をダウンロードして、先ほどのURLにリクエストを送信します。`Durable Functions をローカルでテストする` の章でコピーした、URL をテキストボックスに張り付けて、[Send] を選択します。
![PostMan.JPG](https://qiita-image-store.s3.amazonaws.com/0/3470/433f9a47-0c6d-a3ba-a175-3dcc5276d73e.jpeg)

バックグラウンドでは、Durable Functions が非同期に実行されています。

![Durable01.JPG](https://qiita-image-store.s3.amazonaws.com/0/3470/d0e3dbee-382e-ac67-3ac3-591fe825f3ca.jpeg)

しばらくすると、Postman に結果が返ってきます。これは、処理が終了したという意味ではなく、リクエストの受付が終了したという意味で、バックグラウンドで、オーケストレーションが実行されています。

# ステータスを確認する

`statusQueryGetUri` のリンクをクリックして、[Send] を選択します。このリンクにより、実行されたオーケストレーションのステータスを確認することができます。

![PostMan02.JPG](https://qiita-image-store.s3.amazonaws.com/0/3470/138d725b-e7ec-d0e7-f9ed-c24fc4d718cb.jpeg)

オーケストレーションの実行結果が `output` に表示されます。

| 項目 | 説明 |
|------|-----|
| instanceId | オーケストレーションのId。自動発行されるが、StartAsNew() で指定も可能 |
| runtimeStatus | オーケストレーションの実行状態 Running, Completed, Failure, Terminated 等 |
| input | オーケストレーションのパラメータ StartAsNew() で指定された引数)|
| cutomStatus | カスタムのステータス |
| output | オーケストレーションの実行結果 |
| createdTime | オーケストレーションの開始時間 |
| latelyUpdatedTime | オーケストレーションが最後に動作した時間 |

# 次のステップ

Durable Functions のコーディングの詳細は、[Durable Functions の概要](https://docs.microsoft.com/ja-jp/azure/azure-functions/durable-functions-overview) をご参照ください。ローカルで作成した関数のデバッグや、Azure へのデプロイ方法は [Visual Studio を使用する Azure Functions の開発](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-your-first-function-visual-studio)をごらんください。

* [Durable Functions の概要](https://docs.microsoft.com/ja-jp/azure/azure-functions/durable-functions-overview) 
* [Visual Studio を使用する Azure Functions の開発](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-your-first-function-visual-studio)

# Azure Functions の実行で Could not load file or assembly 問題が起きたら

* [Solving Azure Functions and WebJobs Tools update issue with Visual Studio — Could not load file or assembly ‘Microsoft.AspNetCore.Http.Abstractions,(英語)](https://medium.com/@tsuyoshiushio/solving-azure-functions-and-webjobs-tools-update-issue-with-visual-studio-could-not-load-file-or-fee9f8d30136)