0
Help us understand the problem. What are the problem?

posted at

updated at

Annotations Frameworkを使って、C#でのAWS Lambda 関数実装をもっと楽にしよう

はじめに

Visual Studio で AWS Lambda の新しいプロジェクトを作成しようとしたら、Annotations Framework という見慣れないプロジェクトがありました。今回はこの子がどういったものであるかを確認していきます。
image.png

Lambda Annotations とは?

AWS Labmda Dotnet の Lambda Annotations に関する README を確認すると、下記のような説明があります。

ラムダ注釈デザインは、.NET ラムダ関数を記述するための新しいプログラミングモデルです。大まかに言うと、新しいプログラミング モデルでは慣用的な .NET コーディング パターンが可能になり、C# ソース ジェネレーター テクノロジを使用して、Lambda プログラミング モデルとより慣用的なプログラミング モデルの間のギャップを埋めます。

これまで、.NET で作った Lambda の関数を WebAPI として公開する場合、.NET 側の関数で API Gateway からコンテキストを受け取るように実装し、CloudFormation のテンプレートで APIGateway と Lambda 関数を定義してマッピングしてあげる必要がありました。

もしくは、WebAPI を公開したいという目的であれば、ASP.NET Core のアプリケーションそのものを Lambda 関数として動かして APIGateway と Proxy 統合する方法もあります。この場合は、素の ASP.NET Core のプログラミングモデルをそのまま使えるのでうれしいのですが、Lambda 関数のコールドスタート時に ASP.NET Core の初期化処理が入り時間がかかるので悩ましい部分がありました。

Lambda Annotations を使うと、.NET の関数に定義した属性の値をもとに CloudFormation のコードを自動生成してくれるようです。また、Lambda Annotations が対象にしているのは WebApi だけではなく、S3、SQS、DynamoDb を対象としたトリガーにも対応しているようです。

Lambda Annotations プロジェクトを作成する。

Visual Studio で Annotations Framework を選択してプロジェクトを作成すると、5 個の Lambda 関数(Default, Add, Substract, Multiply, Divide)を定義した Functions.cs と、依存関係の注入の設定を行うための Startup.cs を含むプロジェクトが作成されます。

image.png

Visual Studio Code などを利用する場合、下記のコマンドで Lambda 用のテンプレートをインストールするとプロジェクトを作成できるようになります。

> dotnet new -i Amazon.Lambda.Templates::*
> dotnet new serverless.Annotations

プロジェクト作成直後のプロジェクトファイルは次のようになっています。
Amazon.Lambda.Annotations というパッケージが Annotations の本体のようです。AWS .NET Lambda Mock Test Tool 用の設定も入っているので、ローカルで Lambda 関数のデバックを利用できそうなのも良いですね。

AWSServerless1.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
    <AWSProjectType>Lambda</AWSProjectType>
    <!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. -->
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

    <!-- Generate ready to run images during publishing to improvement cold starts. -->
    <PublishReadyToRun>true</PublishReadyToRun>	 
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
    <PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.4.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.2.0" />
    <PackageReference Include="Amazon.Lambda.Annotations" Version="0.5.0-preview" />
  </ItemGroup>
</Project>

Amazon.Lambda.Annotations が依存関係として net6.0 を持っているので、AWS AnnotatAWS Framework を利用する場合は .NET 6.0 以降が対象になるのは注意点です。

image.png

Lambda 関数の中身を確認する

Functions.cs を簡単に見ていきましょう。
Add メソッドには、Amazon.Lambda.Annotations.LambdaFunction 属性と Amazon.Lambda.Annotations.HttpApi 属性が付与されていることが確認できます。どうやらこれらの属性をもとにソースジェネレーターで CloudFormation テンプレートを更新するようですね。Amazon.Lambda.Annotations 名前空間を見ると、ほかにも利用できそうな属性を確認できます。

Functions.cs
using Amazon.Lambda.Core;
using Amazon.Lambda.Annotations;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace AWSServerless1
{
    /// <summary>
    /// A collection of sample Lambda functions that provide a REST api for doing simple math calculations. 
    /// </summary>
    public class Functions
    {
        public Functions()
        {
        }

        // ... 略 ...

        /// <summary>
        /// Perform x + y
        ///
        /// PackageType is currently required to be set to LambdaPackageType.Image till the upcoming .NET 6 managed
        /// runtime is available. Once the .NET 6 managed runtime is available PackageType will be optional and will
        /// default to Zip.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns>Sum of x and y.</returns>
        [LambdaFunction()]
        [HttpApi(LambdaHttpMethod.Get, "/add/{x}/{y}")]
        public int Add(int x, int y, ILambdaContext context)
        {
            context.Logger.LogInformation($"{x} plus {y} is {x + y}");
            return x + y;
        }

        // ... 略 ...
    }
}

ビルドすると、同じ階層の serverless.template が更新されます。
試しに Functions.cs に次のメソッドを追加してビルドして見ます。

Functions1
namespace AWSServerless1
{
    public class Functions
    {
        // .. 略 ...

        [LambdaFunction]
        [HttpApi(LambdaHttpMethod.Get, "/hello/{message}")]
        public string Hello(string message) => $"Hello {message}";

        // .. 略 ...

AWSServerless1::AWSServerless1.Functions_Hello_Generated::Hello という関数と API Gateway の定義が追加されているのがわかります。

image.png

Lambda Annotations のローカルでのデバック

AWS Toolkit for Visual Studio か、dotnet-lambda-test-tool がインストールされていればローカルで Lambda 関数のデバック実行が行えます。Visual Studio の場合、実行メニューで Mock Lambda Test Tool を指定してデバックを開始すればよいです。Visual Studio Code でデバックする場合は、Configure for Visual Studio Code の手順でデバックできそうです(未確認)。

image.png

Functions に先ほど生成された AWSServerless1.FunctionsHelloGenerated を指定し、Function input に次のような JSON を指定して Execute Function ボタンをクリックすれば、Visual Studio でデバックすることができます。

image.png

Function input
{
    "version": "2.0",
    "routeKey": "GET /hello/{message}",
    "pathParameters": {
        "message": "world"
    },
    "isBase64Encoded": false
}

image.png

依存関係の注入と設定情報の取得

Annotations Framework では、Amazon.Lambda.Annotations.LambdaStartup 属性がついたクラスで依存関係の注入の設定を行うことができます。テンプレートで作成されたプロジェクトでは、Startup.csLambdaStartup 属性がついているのでこのクラスで設定を行っていきます。

ここで設定した依存関係は、各 Lambda 関数のコンストラクタか、メソッドで受け取ることができます。
例えば、IOptions インターフェイスと、AWS S3 の呼び出しを行いたい場合は必要なパッケージを追加した後、

dotnet add package Microsoft.Extensions.Options
dotnet add package AWSSDK.Extensions.NETCore.Setup
dotnet add package AWSSDK.S3

ソリューションエクスプローラーから appsettings.json を追加して、次の設定ファイルを追加します。

appsetting.json
{
  "Setting1": {
    "Prop1": "Hoge",
    "Prop2": "Hogege"
  },
  "AWS": {
    "Profile": "lab",
    "Region": "ap-northeast-1"
  }
}

Lambda 関数の初期化時に ConfigureServices メソッドで設定された依存関係が Lambda 関数のコンストラクタ、もしくはメソッドに依存関係として注入されます。

Startup.cs
    [Amazon.Lambda.Annotations.LambdaStartup]
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            var builder = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json")
                    .AddEnvironmentVariables();

            var configuration = builder.Build();
            services.AddSingleton<IConfiguration>(configuration);
            services.Configure<Setting1>(configuration.GetSection("Setting1"));

            services.AddDefaultAWSOptions(configuration.GetAWSOptions());
            services.AddAWSService<IAmazonS3>();
        }
    }

    public class Setting1
    {
        public string Prop1 { get; set; } = null!;
        public string Prop2 { get; set; } = null!;
    }

メソッドで依存関係を受ける場合、生存期間が Scope or Singleton の場合は FromServices 属性をつけないと例が発生するので注意してください。

Functions.cs
    public class Functions
    {
        private readonly IConfiguration _configuration;
        private readonly IOptions<Setting1> _options;
        public Functions(IConfiguration configuration, IOptions<Setting1> options)
        {
            _configuration = configuration;
            _options = options;
        }


        [LambdaFunction]
        [HttpApi(LambdaHttpMethod.Get, "/hello/{message}")]
        
        public string Hello(
            string message,
            [FromServices] IOptions<Setting1> options
        )
            => $"Hello {message} {_configuration.GetValue<string>("Setting1:Prop1")} {options.Value.Prop1} {_options.Value.Prop2}";
        
        // ... 略 ...
    }

おわりに

Annotation Framework いいですね!
まだ preview リリースなのでこれからも変更が入っていくと思いますが、早くリリースしてほしいです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
0
Help us understand the problem. What are the problem?