[アップデート] AWS Lambda Durable Execution SDK for .NET が GA になったので C# で durable functions を試してみた

[アップデート] AWS Lambda Durable Execution SDK for .NET が GA になったので C# で durable functions を試してみた

AWS Lambda Durable Execution SDK for .NET が GA になりました。Lambda Annotations モデルを使った実装からデプロイ、ローカルテストまで、実際に試してみた内容を紹介します。
2026.07.26

いわさです。

AWS Lambda durable functions は re:Invent 2025 で発表された、Lambda 関数に checkpoint/replay の仕組みを加えて最大1年間のワークフローを構築できる機能です。
既に以下の記事で紹介されています。

https://dev.classmethod.jp/articles/aws-lambda-durable-functions-awsreinvent/

発表当初は JavaScript/TypeScript と Python のみの対応でしたが、その後 [1] され、今回さらに .NET (C#) 向けの Durable Execution SDK が GA になりました。

https://aws.amazon.com/about-aws/whats-new/2026/07/lambdadf-dotnet/

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.

https://docs.aws.amazon.com/durable-execution/sdk-reference/languages/csharp/

今回は 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.DurableExecutionAmazon.Lambda.Core 3.2.0 以上を要求するみたいです。
テンプレートから生成されたプロジェクトでは 2.5.0 が参照されているため、バージョンのアップグレードが必要でした。

ワークフローの実装

.csprojOutputTypeexe に設定し、以下のコードを書きました。

Function.cs
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 の中身を確認してみます。

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: 3ProcessedCount: 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 パッケージを使うと、デプロイせずにローカルでワークフローの動作確認ができます。

https://www.nuget.org/packages/Amazon.Lambda.DurableExecution.Testing

dotnet add package Amazon.Lambda.DurableExecution.Testing

xUnit でテストを書きました。

DurableWorkflowTest.cs
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 資産がある環境での活用の幅が広がりそうです。

脚注
  1. Java SDK が追加 ↩︎

  2. Amazon.Lambda.DurableExecution ↩︎

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事