C# の AWS Lambda 関数ハンドラー
Lambda 関数を作成する際、サービスがユーザーに代わって関数を実行するときに AWS Lambda が呼び出すことができるハンドラーを指定します。
Lambda 関数ハンドラーをクラスのインスタンスまたは静的メソッドとして定義します。Lambda コンテキストオブジェクトへのアクセスは、ILambdaContext 型のメソッドパラメータ、現在の関数、メモリ制限、残り実行時間など現在の実行に関する情報にアクセスするために使用するインターフェイス、ログ記録を定義することで可能になります。
returnType
handler-name
(inputType
input, ILambdaContext context) { ... }
構文では、以下の点に注意してください。
-
inputType
– ハンドラーの最初のパラメータはハンドラーへの入力です。このパラメータには、イベントデータ (イベントソースによって発行される) またはユーザーが提供するカスタム入力 (文字列やカスタムデータオブジェクトなど) を指定できます。 -
returnType
– Lambda 関数を同期的に呼び出す (RequestResponse
呼び出しタイプを使用) 場合は、サポートされているいずれかのデータ型を使用して関数の出力を返すことができます。たとえば、Lambda 関数をモバイルアプリケーションのバックエンドとして使用する場合、これを同期的に呼び出しています。出力データ型は JSON にシリアル化されます。Lambda 関数を非同期的に呼び出す(
Event
呼び出しタイプを使用)計画の場合、returnType
はvoid
である必要があります。たとえば、Amazon S3 や Amazon SNS などのイベントソースとともに AWS Lambda を使用する場合、これらのイベントソースはEvent
呼び出しタイプを使用して Lambda 関数を呼び出します。 -
ILambdaContext context
– ハンドラー署名の 2 番目の引数はオプションです。これは、関数とリクエストに関する情報を持つコンテキストオブジェクトへのアクセスを提供します。
ストリームの処理
System.IO.Stream
タイプのみ入力パラメータとしてデフォルトでサポートされています。
たとえば、次の C# コードの例を考えてみます。
using System.IO; namespace Example { public class Hello { public Stream MyHandler(Stream stream) { //function logic } } }
例の C# コードでは、最初のパラメータはハンドラー (MyHandler) の入力であり、イベントデータ (Amazon S3 などのイベントソースによって発行)、Stream
など指定するカスタム入力 (この例のように)、または任意のカスタムデータオブジェクトとすることができます。出力は Stream
型となります。
標準データ型の処理
ここに示されているそのほかの型は、すべてシリアライザーを指定する必要があります。
-
.NET プリミティブ型 (文字列、整数など)。
-
コレクションとマップ - IList、IEnumerable、IList<T>、Array、IDictionary、IDictionary<TKey, TValue>
-
POCO (Plain old CLR objects) 型
-
定義済み AWS イベントタイプ
-
非同期の呼び出しの際、戻り型は Lambda により無視されます。このような場合、戻り型を void に設定することもできます。
-
.NET 非同期プログラミングを使用している場合、戻り型は Task および Task<T> 型で
async
およびawait
キーワードを使用できます。詳細については、「AWS Lambda で C# 関数の async を使用する」を参照してください。
関数の入力と出力パラメータが System.IO.Stream
型である場合を除き、シリアル化する必要があります。AWS Lambda では、アプリケーションのアセンブリレベルあるいはメソッドレベルに適用できるデフォルトのシリアライザーを提供しています。また、Amazon.Lambda.Core
ライブラリで提供している ILambdaSerializer
インターフェイスを実装して、独自に定義することもできます。詳細については、「C# の AWS Lambdaデプロイパッケージ」を参照してください。
メソッドにデフォルトのシリアライザー属性を追加するには、最初に project.json
ファイルの Amazon.Lambda.Serialization.Json
に依存関係を追加します。
{ "version": "1.0.0-*", "dependencies":{ "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.1" }, "Amazon.Lambda.Serialization.Json": "1.3.0" }, "frameworks": { "netcoreapp1.0": { "imports": "dnxcore50" } } }
以下の例では、あるメソッドでデフォルトの Json.NET シリアライザーを指定し、別のメソッドで選択したものを指定することで、柔軟な利用が可能であることを示しています。
public class ProductService{ [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] public Product DescribeProduct(DescribeProductRequest request) { return catalogService.DescribeProduct(request.Id); } [LambdaSerializer(typeof(MyJsonSerializer))] public Customer DescribeCustomer(DescribeCustomerRequest request) { return customerService.DescribeCustomer(request.Id); } }
.NET Core 3.1 を使用している場合は、Amazon.Lambda.Serialization.SystemTextJsonAmazon.Lambda.Serialization.Json
のパフォーマンスを向上させます。
ハンドラー署名
Lambda 関数を作成する場合、呼び出すコードをどこで探すか AWS Lambda に指示するハンドラー文字列を指定する必要があります。C# では、次の形式になります。
ASSEMBLY::TYPE::METHOD
where:
-
ASSEMBLY
は、アプリケーションの .NET アセンブリファイルの名前です。.NET Core CLI を使用してアプリケーションを構築する場合、project.json でbuildOptions.outputName
設定を使用してアセンブリ名を設定していない場合、ASSEMBLY
名が project.json ファイルを含むフォルダの名前になります。詳細については、「.NET Core CLI」を参照してください。この場合、フォルダ名はHelloWorldApp
だと仮定します。 -
TYPE
は、Namespace
とClassName
からなるハンドラー型の正式名称となります。この場合、Example.Hello
です。 -
METHOD
は、関数ハンドラー名で、この場合MyHandler
です。
最終的に、署名の形式は、Assembly::Namespace.ClassName::MethodName
となります。
再び次の例を考えます。
using System.IO; namespace Example { public class Hello { public Stream MyHandler(Stream stream) { //function logic } } }
ハンドラー文字列は次のようになります。 HelloWorldApp::Example.Hello::MyHandler
ハンドラー文字列で指定されたメソッドが過負荷である場合、Lambda が呼び出す必要のあるメソッドの正確な署名を指定しなければなりません。AWS Lambda は、解決で複数の (過負荷の) の署名から選択することが要求される場合に、その他の有効な署名を拒否します。
Lambda 関数のシリアル化
Stream
オブジェクト以外の入出力タイプを使用するすべての Lambda 関数には、アプリケーションにシリアル化ライブラリを追加する必要があります。これは以下の方法で対応できます。
-
Amazon.Lambda.Serialization.Json
NuGet パッケージを使用します。このライブラリでは、シリアル化を処理するために JSON.NET が使用されます。注記 .NET Core 3.1 を使用している場合は、Amazon.Lambda.Serialization.SystemTextJson
シリアライザーを使用することをお勧めします。このパッケージは、 Amazon.Lambda.Serialization.Json
のパフォーマンスを向上させます。 -
ILambdaSerializer
インターフェイスの実装によって独自のシリアル化ライブラリを作成します。これは、Amazon.Lambda.Core
ライブラリの一部として入手できます。インターフェイスは 2 つのメソッドを定義します。-
T Deserialize<T>(Stream requestStream);
このメソッドを実装して、
Invoke
API から Lambda 関数ハンドラーに渡されるオブジェクトにリクエストのペイロードを逆シリアル化することができます。 -
T Serialize<T>(T response, Stream responseStream);
このメソッドを実装して、Lambda 関数ハンドラーから返される結果を
Invoke
API から返されたレスポンスペイロードにシリアル化することができます。
-
任意のシリアライザーを使用するため、MyProject.csproj
ファイルに依存関係として追加します。
... <ItemGroup> <PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" /> <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.3.0" /> </ItemGroup>
次に、それを AssemblyInfo.cs ファイルに追加します。たとえば、デフォルトの Json.NET シリアライザーを使用している場合は、以下を追加します。
[assembly:LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
メソッドレベルでカスタムシリアル化属性を定義することができます。それにより、アセンブリレベルで指定されたデフォルトのシリアライザーが上書きされます。詳細については、「標準データ型の処理」を参照してください。
Lambda 関数ハンドラーの制限
ハンドラー署名にはいくつかの制限があることに注意してください。
-
unsafe
コンテキストは、ハンドラーメソッドとその依存関係の内部で使用できますが、ハンドラー署名にunsafe
がなくポインタ型を使用できない場合があります。詳細については、「unsafe (C# リファレンス)」を参照してください。 -
params
キーワードを使用して可変数のパラメータを渡さなかったり、可変数のパラメータをサポートするために使用する入力パラメータまたは戻りパラメータとしてArgIterator
を使用できない場合があります。 -
ハンドラーは汎用メソッドでないことがあります (例: IList<T> Sort<T>(IList<T> input))。
-
async void
署名を持つ Async ハンドラーはサポートされていません。
AWS Lambda で C# 関数の async を使用する
大容量ファイルを Amazon S3 にアップロードする、DynamoDB から大量のストリームのレコードを読み込むなど、Lambda 関数で長時間実行するプロセスが必要になることがわかっている場合は、async/await パターンを利用できます。この署名を使用すると、Lambda は関数を同期的に呼び出し、関数からレスポンスが返るまで、または実行がタイムアウトするまで待機します。
public async Task<Response> ProcessS3ImageResizeAsync(SimpleS3Event input) { var response = await client.DoAsyncWork(input); return response; }
このパターンを使用する場合は、いくつかの考慮事項を検討してください。
-
AWS Lambda では
async void
メソッドはサポートされていません。 -
await
演算子を実装しないで非同期 Lambda 関数を作成すると、.NET がコンパイラの警告を出し、予期しない動作が発生します。例えば、一部の非同期アクションが実行されないことがあります。また、関数の実行が完了する前に、一部の非同期アクションが完了しないことがあります。public async Task ProcessS3ImageResizeAsync(SimpleS3Event event) // Compiler warning { client.DoAsyncWork(input); }
-
Lambda 関数は複数の非同期呼び出しを含めることができ、また並行で呼び出すことができます。複数のタスクを使用するため、
Task.WhenAll
およびTask.WhenAny
メソッドを使用できます。Task.WhenAll
メソッドを使用するには、配列としてオペレーションのリストをメソッドに渡します。以下の例では、配列へ一部の操作を含めるのを怠ると、その操作が完了する前に呼び出しが返されることに注意してください。public async Task DoesNotWaitForAllTasks1() { // In Lambda, Console.WriteLine goes to CloudWatch Logs. var task1 = Task.Run(() => Console.WriteLine("Test1")); var task2 = Task.Run(() => Console.WriteLine("Test2")); var task3 = Task.Run(() => Console.WriteLine("Test3")); // Lambda may return before printing "Test2" since we never wait on task2. await Task.WhenAll(task1, task3); }
Task.WhenAny
メソッドを使用するには、配列としてオペレーションのリストを再度メソッドに渡します。最初の操作が完了すると、他がまだ実行中でも呼び出しがすぐに返されます。public async Task DoesNotWaitForAllTasks2() { // In Lambda, Console.WriteLine goes to CloudWatch Logs. var task1 = Task.Run(() => Console.WriteLine("Test1")); var task2 = Task.Run(() => Console.WriteLine("Test2")); var task3 = Task.Run(() => Console.WriteLine("Test3")); // Lambda may return before printing all tests since we're only waiting for one to finish. await Task.WhenAny(task1, task2, task3); }