Durable Functions

# Durable Functionsとは？

Azure Durable Functionsは、Azure Functionsの拡張機能であり、状態を持った関数のオーケストレーションを可能にするフレームワークです。長時間実行する処理や複雑なワークフローの管理に便利です。今回はタイムアウトしてしまっていたプロセスをDurable Functionsにして構築しなおして解決しましたので、その後ちょっと調べてまとめています。

**本記事では、Durable FunctionsのIsolated Worker Process（.NET 6以降）を基本として解説します。**

公式ドキュメントはこちら:
[Durable Functions Overview](https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview?tabs=in-process%2Cnodejs-v3%2Cv1-model&pivots=csharp)

## Durable Functionsを使うケース
Durable Functionsを利用すると、複雑な処理を簡潔に管理できます。代表的なユースケースを以下に紹介します。

### Function Chaining
複数の関数を直列に実行するケース。前の関数の出力を次の関数の入力に利用します。

![Function Chaining](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/247c2ada-e33f-453c-b188-c37fee5fffe1.png)

#### Example Code (Isolated Mode)
```csharp
[Function("FunctionChaining")]  
public static async Task<List<string>> RunOrchestrator( 
    [OrchestrationTrigger] TaskOrchestrationContext context) 
{  
    var results = new List<string>();
    results.Add(await context.CallActivityAsync<string>("Hello", "Step 1"));
    results.Add(await context.CallActivityAsync<string>("Hello", "Step 2"));
    results.Add(await context.CallActivityAsync<string>("Hello", "Step 3"));
    return results;  
}  

[Function("Hello")]  
public static string SayHello([ActivityTrigger] string name) 
{  
    return $"Hello, {name}!";  
}
```

### Fan Out / Fan In
並列処理が必要な場合に使用します。複数の関数を並列実行し、その結果を統合します。

![Fan out / Fan In](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/65a1e2f6-5003-4f17-bd0d-24ef25b8756a.png)

#### Example Code
```csharp
[Function("FanOutFanIn")]  
public static async Task<int> RunOrchestrator( 
    [OrchestrationTrigger] TaskOrchestrationContext context) 
{  
    var tasks = new List<Task<int>>();
    for (int i = 0; i < 5; i++)  
    {  
        tasks.Add(context.CallActivityAsync<int>("ProcessData", i));  
    }  
    int[] results = await Task.WhenAll(tasks);
    return results.Sum();
}  

[Function("ProcessData")]  
public static int ProcessData([ActivityTrigger] int input) 
{  
    return input * 2;  
}
```

### Async HTTP APIs
非同期でHTTP APIを呼び出し、処理完了後に結果を取得するパターン。

![Async HTTP APIs](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/451085e3-6dfb-4b18-be50-f5381449403a.png)

#### Example Code
```csharp
[Function("HttpStart")]  
public static async Task<HttpResponseData> HttpStart( 
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req, 
    [DurableClient] DurableTaskClient client) 
{  
    string instanceId = await client.StartNewAsync("AsyncOrchestration", null);
    return client.CreateCheckStatusResponse(req, instanceId);
}
```

### Monitor
定期的に特定の状態をチェックする必要があるケース。ポーリングを実装する場合に便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/cbd27754-3507-4129-a2f9-8f2df4cde231.png)


#### Example Code
```csharp
[Function("Monitor")]  
public static async Task RunOrchestrator( 
    [OrchestrationTrigger] TaskOrchestrationContext context) 
{  
    while (true) 
    {  
        bool isConditionMet = await context.CallActivityAsync<bool>("CheckStatus", null);
        if (isConditionMet) break;
        await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(30), CancellationToken.None);
    }  
}  
```

### Human Interaction
ユーザーの入力を待つ必要があるワークフローを作成できます。

![Human Interaction](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/67e77614-c91b-415f-adc6-a4f5a84ac185.png)

#### Example Code
```csharp
[Function("WaitForUser")]  
public static async Task<string> RunOrchestrator( 
    [OrchestrationTrigger] TaskOrchestrationContext context) 
{  
    return await context.WaitForExternalEvent<string>("UserResponse");
}  
```

## Orchestrator Codeの制限
Orchestrator Functionは決定論的である必要があります。そのため、以下のような制約があります。
- **現在時刻の取得禁止**: `DateTime.Now` や `Guid.NewGuid()` は使用不可。
- **HTTPリクエストの実行禁止**: 外部API呼び出しは`Activity Function`で行う必要がある。
- **スレッド操作の禁止**: `Task.Delay` や `Thread.Sleep` の使用は避ける。