[アップデート] AWS Lambda Durable Execution SDK for .NET が GA になったので C# で durable functions を試してみた
いわさです。
AWS Lambda durable functions は re:Invent 2025 で発表された、Lambda 関数に checkpoint/replay の仕組みを加えて最大1年間のワークフローを構築できる機能です。
既に以下の記事で紹介されています。
発表当初は JavaScript/TypeScript と Python のみの対応でしたが、その後 [1] され、今回さらに .NET (C#) 向けの Durable Execution SDK が GA になりました。
NuGet パッケージ [2] として提供されており、既存の .NET Lambda 開発のツールチェーンにそのまま統合できます。
今回こちらを確認してみたので紹介します。
Lambda Annotations モデルで使ってみる
公式ドキュメントによると、.NET SDK では3つのプログラミングモデル(executable / class-library / Lambda Annotations)がサポートされているみたいです。
The managed .NET runtime supports three ways to host a durable function.
今回は Lambda Annotations モデルを使って検証してみました。
Annotations モデルでは [LambdaFunction] と [DurableExecution] のアトリビュートを付けるだけで durable function を定義でき、ソースジェネレーターが CloudFormation テンプレート(DurableConfig や IAM ポリシー含む)まで自動生成してくれます。
プロジェクトのセットアップ
dotnet new lambda.EmptyFunction でプロジェクトを作成し、必要なパッケージを追加しました。
dotnet new lambda.EmptyFunction --name HogeDurable --output HogeDurable --profile hoge --region us-east-1
cd HogeDurable/src/HogeDurable
dotnet add package Amazon.Lambda.DurableExecution
dotnet add package Amazon.Lambda.Annotations
dotnet add package Amazon.Lambda.RuntimeSupport
dotnet add package Microsoft.Extensions.Logging.Abstractions
公式ドキュメントによると、Amazon.Lambda.DurableExecution が Amazon.Lambda.Core 3.2.0 以上を要求するみたいです。
テンプレートから生成されたプロジェクトでは 2.5.0 が参照されているため、バージョンのアップグレードが必要でした。
ワークフローの実装
.csproj に OutputType を exe に設定し、以下のコードを書きました。
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Core;
using Amazon.Lambda.DurableExecution;
using Amazon.Lambda.Serialization.SystemTextJson;
using Microsoft.Extensions.Logging;
[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
[assembly: LambdaGlobalProperties(GenerateMain = true)]
namespace HogeDurable;
public record HogeInput(string Name, int Count);
public record HogeResult(string Message, int ProcessedCount, string Status);
public class Function
{
[LambdaFunction]
[DurableExecution(900, RetentionPeriodInDays = 1)]
public async Task<HogeResult> Workflow(HogeInput input, IDurableContext ctx)
{
// Step 1: 入力のバリデーション
var validated = await ctx.StepAsync(
async (_, ct) =>
{
ctx.Logger.LogInformation("Validating input: {Name}", input.Name);
await Task.Delay(100, ct);
return $"validated-{input.Name}";
},
name: "validate-input");
// Step 2: データの処理
var processed = await ctx.StepAsync(
async (_, ct) =>
{
ctx.Logger.LogInformation("Processing {Count} items for {Name}", input.Count, validated);
await Task.Delay(200, ct);
return input.Count * 2;
},
name: "process-data");
// 外部処理の完了を待つシミュレーション
await ctx.WaitAsync(TimeSpan.FromSeconds(5), name: "wait-for-approval");
// Step 3: 結果の確定
var result = await ctx.StepAsync(
async (_, ct) =>
{
ctx.Logger.LogInformation("Finalizing result for {Name}", validated);
await Task.Delay(100, ct);
return new HogeResult(
$"Hello {input.Name}, processing complete!",
processed,
"completed");
},
name: "finalize-result");
return result;
}
}
[DurableExecution(900, RetentionPeriodInDays = 1)] で、実行タイムアウト 900秒、実行履歴の保持期間 1日を指定しています。
各ステップは ctx.StepAsync でラップし、name パラメータでチェックポイントの名前を付けます。
ctx.WaitAsync で外部イベントの待機をシミュレートしています。
なお、同ドキュメントによると IDurableContext.Logger は replay-safe な ILogger で、リプレイ時にログが重複出力されないみたいです。
IDurableContext.Logger is a replay-safe Microsoft.Extensions.Logging.ILogger. It suppresses messages emitted while the workflow re-derives prior operations from checkpointed state, so a 30-step workflow re-invoked 30 times still emits each line once.
ソースジェネレーターによる serverless.template の自動生成
dotnet build でビルドすると、Annotations のソースジェネレーターが serverless.template を自動生成してくれます。
dotnet build
HogeDurable net8.0 succeeded (0.7s) → bin/Debug/net8.0/HogeDurable.dll
Build succeeded in 1.4s
生成された serverless.template の中身を確認してみます。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
"Description": "This template is partially managed by Amazon.Lambda.Annotations (v2.3.0.0).",
"Resources": {
"HogeDurableFunctionWorkflowGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
"SyncedDurableConfig": true,
"SyncedDurablePolicy": true
},
"Properties": {
"Runtime": "dotnet8",
"CodeUri": ".",
"MemorySize": 512,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole",
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy"
],
"PackageType": "Zip",
"Handler": "HogeDurable",
"Environment": {
"Variables": {
"ANNOTATIONS_HANDLER": "Workflow"
}
},
"DurableConfig": {
"RetentionPeriodInDays": 1,
"ExecutionTimeout": 900
}
}
}
}
}
DurableConfig ブロックと AWSLambdaBasicDurableExecutionRolePolicy が自動的に含まれています。
コード側のアトリビュートで指定した値がそのまま反映されるので、テンプレートを手動で編集する必要がありません。
デプロイ
dotnet lambda deploy-serverless でデプロイしました。
dotnet lambda deploy-serverless --stack-name hoge-durable-stack \
--s3-bucket hoge-durable-deploy-bucket-0726 \
--template serverless.template --region us-east-1
Created CloudFormation stack hoge-durable-stack
Timestamp Logical Resource Id Status
-------------------- ---------------------------------------- ----------------------------------------
Stack finished updating with status: CREATE_COMPLETE
デプロイ後、Lambda 関数が作成されていることが確認できます。
動作確認
durable functions はバージョン付き(qualified ARN)で呼び出す必要があります。
まずバージョンをパブリッシュします。
aws lambda publish-version \
--function-name hoge-durable-stack-HogeDurableFunctionWorkflowGene-GHf3k07Tz7jp \
--region us-east-1 --description "Initial version"
レスポンスの DurableConfig セクションで durable execution が有効であることが確認できます。
{
"Version": "1",
"DurableConfig": {
"RetentionPeriodInDays": 1,
"ExecutionTimeout": 900
}
}
バージョン :1 を指定して呼び出します。
aws lambda invoke \
--function-name "hoge-durable-stack-HogeDurableFunctionWorkflowGene-GHf3k07Tz7jp:1" \
--payload '{"Name": "hoge", "Count": 3}' \
--cli-binary-format raw-in-base64-out \
--region us-east-1 /tmp/durable-output.json
{
"StatusCode": 200,
"ExecutedVersion": "1",
"DurableExecutionArn": "arn:aws:lambda:us-east-1:123456789012:function:hoge-durable-stack-HogeDurableFunctionWorkflowGene-GHf3k07Tz7jp:1/durable-execution/56d85076-34aa-43a1-9591-553895b8d5e6/693561eb-f88e-3b79-97e5-12c25d87ae67"
}
レスポンスに DurableExecutionArn が含まれています。
出力ファイルの中身を確認すると、期待どおりの結果が返ってきました。
{"Message":"Hello hoge, processing complete!","ProcessedCount":6,"Status":"completed"}
Count: 3 が ProcessedCount: 6(3 × 2)になっており、ワークフロー内の処理が正しく実行されています。
実行履歴の確認
list-durable-executions-by-function で実行一覧を確認できます。
aws lambda list-durable-executions-by-function \
--function-name "hoge-durable-stack-HogeDurableFunctionWorkflowGene-GHf3k07Tz7jp" \
--region us-east-1
{
"DurableExecutions": [
{
"DurableExecutionName": "56d85076-34aa-43a1-9591-553895b8d5e6",
"Status": "SUCCEEDED",
"StartTimestamp": "2026-07-26T14:51:45.366000+09:00",
"EndTimestamp": "2026-07-26T14:51:52.711000+09:00"
}
]
}
get-durable-execution-history で各チェックポイントの詳細も確認できます。
aws lambda get-durable-execution-history \
--durable-execution-arn "arn:aws:lambda:us-east-1:123456789012:function:hoge-durable-stack-HogeDurableFunctionWorkflowGene-GHf3k07Tz7jp:1/durable-execution/56d85076-34aa-43a1-9591-553895b8d5e6/693561eb-f88e-3b79-97e5-12c25d87ae67" \
--region us-east-1
{
"Events": [
{ "EventType": "ExecutionStarted", "EventId": 1, "EventTimestamp": "2026-07-26T14:51:45.366000+09:00" },
{ "EventType": "StepStarted", "Name": "validate-input", "EventId": 2, "EventTimestamp": "2026-07-26T14:51:47.064000+09:00" },
{ "EventType": "StepSucceeded", "Name": "validate-input", "EventId": 3, "EventTimestamp": "2026-07-26T14:51:47.193000+09:00" },
{ "EventType": "StepStarted", "Name": "process-data", "EventId": 4, "EventTimestamp": "2026-07-26T14:51:47.267000+09:00" },
{ "EventType": "StepSucceeded", "Name": "process-data", "EventId": 5, "EventTimestamp": "2026-07-26T14:51:47.444000+09:00" },
{ "EventType": "WaitStarted", "Name": "wait-for-approval", "EventId": 6, "EventTimestamp": "2026-07-26T14:51:47.492000+09:00",
"WaitStartedDetails": { "Duration": 5, "ScheduledEndTimestamp": "2026-07-26T14:51:52.492000+09:00" }
},
{ "EventType": "InvocationCompleted", "EventId": 7, "EventTimestamp": "2026-07-26T14:51:47.576000+09:00" },
{ "EventType": "WaitSucceeded", "Name": "wait-for-approval", "EventId": 8, "EventTimestamp": "2026-07-26T14:51:52.492000+09:00" },
{ "EventType": "StepStarted", "Name": "finalize-result", "EventId": 9, "EventTimestamp": "2026-07-26T14:51:52.608000+09:00" },
{ "EventType": "StepSucceeded", "Name": "finalize-result", "EventId": 10, "EventTimestamp": "2026-07-26T14:51:52.688000+09:00" },
{ "EventType": "InvocationCompleted", "EventId": 11, "EventTimestamp": "2026-07-26T14:51:52.711000+09:00" },
{ "EventType": "ExecutionSucceeded", "EventId": 12, "EventTimestamp": "2026-07-26T14:51:52.711000+09:00" }
]
}
EventId 7 の InvocationCompleted で一度関数の実行が終了し、5秒後(Wait の完了後)に再度呼び出されて EventId 9 から処理が再開されていることがわかります。
これが durable functions の checkpoint/replay の仕組みで、Wait 中は Lambda のコンピュート料金が発生しません。
ローカルテスト SDK で検証する
Amazon.Lambda.DurableExecution.Testing パッケージを使うと、デプロイせずにローカルでワークフローの動作確認ができます。
dotnet add package Amazon.Lambda.DurableExecution.Testing
xUnit でテストを書きました。
using Amazon.Lambda.DurableExecution;
using Amazon.Lambda.DurableExecution.Testing;
using HogeDurable;
using Xunit;
namespace HogeDurable.Tests;
public class DurableWorkflowTest
{
private static async Task<HogeResult> Workflow(HogeInput input, IDurableContext ctx)
{
var validated = await ctx.StepAsync(
async (_, ct) =>
{
await Task.Delay(10, ct);
return $"validated-{input.Name}";
},
name: "validate-input");
var processed = await ctx.StepAsync(
async (_, ct) =>
{
await Task.Delay(10, ct);
return input.Count * 2;
},
name: "process-data");
await ctx.WaitAsync(TimeSpan.FromSeconds(5), name: "wait-for-approval");
var result = await ctx.StepAsync(
async (_, ct) =>
{
await Task.Delay(10, ct);
return new HogeResult(
$"Hello {input.Name}, processing complete!",
processed,
"completed");
},
name: "finalize-result");
return result;
}
[Fact]
public async Task Workflow_ReturnsExpectedResult()
{
await using var runner = new DurableTestRunner<HogeInput, HogeResult>(
Workflow,
new TestRunnerOptions { SkipTime = true });
var result = await runner.RunAsync(new HogeInput("fuga", 5));
result.EnsureSucceeded();
Assert.Equal("Hello fuga, processing complete!", result.Result.Message);
Assert.Equal(10, result.Result.ProcessedCount);
Assert.Equal("completed", result.Result.Status);
}
[Fact]
public async Task Workflow_ProcessesCountCorrectly()
{
await using var runner = new DurableTestRunner<HogeInput, HogeResult>(
Workflow,
new TestRunnerOptions { SkipTime = true });
var result = await runner.RunAsync(new HogeInput("piyo", 100));
result.EnsureSucceeded();
Assert.Equal(200, result.Result.ProcessedCount);
}
}
DurableTestRunner<TIn, TOut> にワークフローのデリゲートを渡し、RunAsync で実行します。
なお、テストではログ出力を省略したシンプル版のワークフローを定義しています。テストランナーは IDurableContext のモックを内部で構築するため、Lambda のハンドラーメソッドを直接呼ぶのではなく、ワークフローロジックだけを切り出して渡す形になります。
TestRunnerOptions { SkipTime = true } を指定すると、WaitAsync のタイマーが即座に完了するため、テスト全体が高速に実行されます。
dotnet test
[xUnit.net 00:00:00.29] Starting: HogeDurable.Tests
[xUnit.net 00:00:00.45] Finished: HogeDurable.Tests
HogeDurable.Tests test net8.0 succeeded (1.1s)
Test summary: total: 2, failed: 0, succeeded: 2, skipped: 0, duration: 1.0s
Build succeeded in 2.6s
2件のテストがすべてパスしました。
5秒の Wait を含むワークフローが1秒で完了しており、SkipTime が効いていることが確認できます。
さいごに
本日は AWS Lambda Durable Execution SDK for .NET が GA になったので、Lambda Annotations モデルでの実装とデプロイを確認してみました。
[DurableExecution] アトリビュートを付けるだけで DurableConfig や IAM ポリシーが自動生成される点は、.NET 開発者にとって嬉しい体験ですね。
ローカルテスト SDK も DurableTestRunner で手軽にワークフロー全体を検証でき、CI に組み込みやすそうです。
これまで JavaScript/TypeScript や Python でしか使えなかった durable functions が C# でも使えるようになったことで、既存の .NET Lambda 資産がある環境での活用の幅が広がりそうです。








