TypeScript の Lambda 関数ハンドラーの定義 - AWS Lambda

TypeScript の Lambda 関数ハンドラーの定義

Lambda 関数ハンドラーは、イベントを処理する関数コード内のメソッドです。関数が呼び出されると、Lambda はハンドラーメソッドを実行します。関数は、ハンドラーが応答を返すか、終了するか、タイムアウトするまで実行されます。

Typescript ハンドラーの基本

例 TypeScript ハンドラー

この例の関数では、イベントオブジェクトの内容をログに記録して、そのログの場所を返します。次の点に注意してください。

  • このコードを Lambda 関数で使用する前に、開発環境の依存関係として @types/aws-lambda パッケージを追加する必要があります。このパッケージには Lambda の型定義が含まれています。@types/aws-lambda がインストールされると、import ステートメント (import ... from 'aws-lambda') は型定義をインポートします。aws-lambda NPM パッケージはインポートされません。これは関連性のないサードパーティーのツールです。詳細については、「DefinitelyTyped GitHub リポジトリ」の「aws-lambda」を参照してください。

  • この例でのハンドラーは ES モジュールであり、package.json ファイル内で、または .mjs ファイル拡張子を使用して、そのように指定する必要があります。詳細については、「ES モジュールとしての関数ハンドラーの指定」を参照してください。

import { Handler } from 'aws-lambda'; export const handler: Handler = async (event, context) => { console.log('EVENT: \n' + JSON.stringify(event, null, 2)); return context.logStreamName; };

ランタイムでは、ハンドラーメソッドに引数を渡します。最初の引数は、呼び出し元からの情報を含む event オブジェクトです。呼び出し元は、Invoke を呼び出してこの情報を JSON 形式の文字列として渡し、ランタイムはそれをオブジェクトに変換します。AWS のサービスで関数を呼び出す場合、そのイベント構造はサービスによって異なります。TypeScript では、イベントオブジェクトに型注釈を使用することをお勧めします。詳細については、「イベントオブジェクトへの型の使用」を参照してください。

2 番目の引数は、コンテキストオブジェクトです。この引数には、呼び出し、関数、および実行環境に関する情報が含まれます。前述の例では、関数は、コンテキストオブジェクトからログストリームの名前を取得し、それを呼び出し元に返します。

コールバック引数を使用することもできます。これは、非同期ではないハンドラーで呼び出してレスポンスを送信できる関数です。コールバックの代わりに async/await を使用することをお勧めします。async/await により、読みやすさ、エラー処理、および効率が向上します。async/await とコールバックの違いの詳細については、「コールバックの使用」を参照してください。

async/await の使用

コードが非同期タスクを実行する場合は、async/await パターンを使用して、ハンドラーが実行を確実に終了するようにします。async/await は、Node.js で非同期コードを記述するための簡潔で読みやすい方法であり、ネストされたコールバックや連鎖する promise を必要としません。async/await を使用すると、非同期かつノンブロッキングでありながら、同期コードのように読み取るコードを記述できます。

async キーワードは関数を非同期としてマークし、await キーワードは Promise が解決されるまで関数の実行を一時停止します。

例 TypeScript 関数 — 非同期

この例では、nodejs18.x ランタイムで使用できる fetch を使用します。次の点に注意してください。

  • このコードを Lambda 関数で使用する前に、開発環境の依存関係として @types/aws-lambda パッケージを追加する必要があります。このパッケージには Lambda の型定義が含まれています。@types/aws-lambda がインストールされると、import ステートメント (import ... from 'aws-lambda') は型定義をインポートします。aws-lambda NPM パッケージはインポートされません。これは関連性のないサードパーティーのツールです。詳細については、「DefinitelyTyped GitHub リポジトリ」の「aws-lambda」を参照してください。

  • この例でのハンドラーは ES モジュールであり、package.json ファイル内で、または .mjs ファイル拡張子を使用して、そのように指定する必要があります。詳細については、「ES モジュールとしての関数ハンドラーの指定」を参照してください。

import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; const url = 'https://aws.amazon.com/'; export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { try { // fetch is available with Node.js 18 const res = await fetch(url); return { statusCode: res.status, body: JSON.stringify({ message: await res.text(), }), }; } catch (err) { console.log(err); return { statusCode: 500, body: JSON.stringify({ message: 'some error happened', }), }; } };

コールバックの使用

コールバックを使用する代わりに、async/await を使用して関数ハンドラーを宣言することをお勧めします。いくつかの理由により、async/await の方が適しています。

  • 読みやすさ: async/await コードは、コールバックコードよりも読みやすく理解しやすいです。コールバックコードは、すぐに理解するのが難しくなり、コールバック地獄に陥る可能性があります。

  • デバッグとエラー処理: コールバックベースのコードのデバッグは難しい場合があります。コールスタックを追跡するのが難しくなり、エラーが簡単に隠れてしまう可能性があります。async/await では、try/catch ブロックを使用してエラーを処理できます。

  • 効率: コールバックでは、多くの場合、コードのさまざまな部分を切り替える必要があります。async/await を使用すると、コンテキストスイッチの回数を減らすことができるため、コードがより効率的になります。

ハンドラーでコールバックを使用すると、関数は、イベントループが空になるか、関数がタイムアウトするまで実行を続けます。レスポンスは、すべてのイベントループタスクが完了するまで、呼び出し元に送信されません。関数がタイムアウトした場合は、エラーが返ります。すぐにレスポンスが返るようにランタイムを設定するには、context.callbackWaitsForEmptyEventLoop を false に設定します。

コールバック関数は、Error とレスポンスの 2 つの引数を取ります。応答オブジェクトは、JSON.stringify と互換性がある必要があります。

例 コールバックを持つ TypeScript 関数

このサンプル関数は Amazon API Gateway からイベントを受け取り、イベントとコンテキストオブジェクトをログに記録して、API ゲートウェイ にレスポンスを返します。次の点に注意してください。

  • このコードを Lambda 関数で使用する前に、開発環境の依存関係として @types/aws-lambda パッケージを追加する必要があります。このパッケージには Lambda の型定義が含まれています。@types/aws-lambda がインストールされると、import ステートメント (import ... from 'aws-lambda') は型定義をインポートします。aws-lambda NPM パッケージはインポートされません。これは関連性のないサードパーティーのツールです。詳細については、「DefinitelyTyped GitHub リポジトリ」の「aws-lambda」を参照してください。

  • この例でのハンドラーは ES モジュールであり、package.json ファイル内で、または .mjs ファイル拡張子を使用して、そのように指定する必要があります。詳細については、「ES モジュールとしての関数ハンドラーの指定」を参照してください。

import { Context, APIGatewayProxyCallback, APIGatewayEvent } from 'aws-lambda'; export const lambdaHandler = (event: APIGatewayEvent, context: Context, callback: APIGatewayProxyCallback): void => { console.log(`Event: ${JSON.stringify(event, null, 2)}`); console.log(`Context: ${JSON.stringify(context, null, 2)}`); callback(null, { statusCode: 200, body: JSON.stringify({ message: 'hello world', }), }); };

イベントオブジェクトへの型の使用

型をチェックする機能が失われるため、ハンドラー引数と return 型に any 型を使用しないことをお勧めします。代わりに、sam local generate-event AWS Serverless Application Model CLI コマンドを使用してイベントを生成するか、または @types/aws-lambda パッケージからオープンソース定義を使用します。

sam local generate-event コマンドを使用したイベントの生成
  1. Amazon Simple Storage Service (Amazon S3) プロキシイベントを生成します。

    sam local generate-event s3 put >> S3PutEvent.json
  2. QuickType ユーティリティを使用して、S3PutEvent.json ファイルから型定義を生成します。

    npm install -g quicktype quicktype S3PutEvent.json -o S3PutEvent.ts
  3. 生成された型をコードで使用します。

    import { S3PutEvent } from './S3PutEvent'; export const lambdaHandler = async (event: S3PutEvent): Promise<void> => { event.Records.map((record) => console.log(record.s3.object.key)); };
@types/aws-lambda パッケージのオープンソース定義を使用したイベントの生成
  1. 開発環境の依存関係として @types/aws-lambda パッケージを追加します。

    npm install -D @types/aws-lambda
  2. コードで型を使用します。

    import { S3Event } from "aws-lambda"; export const lambdaHandler = async (event: S3Event): Promise<void> => { event.Records.map((record) => console.log(record.s3.object.key)); };

Typescript Lambda 関数のコードのベストプラクティス

Lambda 関数をビルドするときは、次のリストのガイドラインに従って、コーディングのベストプラクティスを実行してください。

  • Lambda ハンドラーをコアロジックから分離します。これにより、関数の単体テストが実行しやすくなります。Node.js では、次のようになります。

    exports.myHandler = function(event, context, callback) { var foo = event.foo; var bar = event.bar; var result = MyLambdaFunction (foo, bar); callback(null, result); } function MyLambdaFunction (foo, bar) { // MyLambdaFunction logic here }
  • 関数のデプロイパッケージ内で依存関係を制御します。AWS Lambda 実行環境には多数のライブラリが含まれています。Node.js および Python ランタイムの場合、ライブラリには AWS SDK が含まれます。最新の機能やセキュリティ更新プログラムを有効にするために、Lambda はこれらのライブラリを定期的に更新します。この更新により、Lambda 関数の動作が微妙に変化する場合があります。関数で使用する依存関係を完全に制御するには、すべての依存関係をデプロイパッケージでパッケージングします。

  • 依存関係の複雑さを最小限に抑えます。フレームワークを単純化して、実行環境起動時のロードを高速化します。

  • デプロイパッケージをランタイムに必要な最小限のサイズにします。これにより、呼び出しに先立ってデプロイパッケージをダウンロードして解凍する所要時間が短縮されます。

  • 実行環境の再利用を活用して関数のパフォーマンスを向上させます。関数ハンドラー外で SDK クライアントとデータベース接続を初期化し、静的なアセットを /tmp ディレクトリにローカルにキャッシュします。関数の同じインスタンスで処理された後続の呼び出しは、これらのリソースを再利用できます。これにより、関数の実行時間が短縮され、コストが節約されます。

    呼び出し間でデータが漏れるのを防ぐため、実行環境を使用してセキュリティ上の懸念があるユーザーデータ、イベント、またはその他の情報を保存しないでください。関数がハンドラー内のメモリに保存できない変更可能な状態に依存している場合は、ユーザーごとに個別の関数または個別のバージョンの関数を作成することを検討してください。

  • keep-alive ディレクティブを使用して永続的な接続を維持します。Lambda は、時間の経過とともにアイドル状態の接続を消去します。関数を呼び出すときにアイドル状態の接続を再利用しようとすると、接続エラーが発生します。永続的な接続を維持するには、ランタイムに関連付けられている keep-alive ディレクティブを使用します。例については、「Node.js で Keep-alive を使用して接続を再利用する」を参照してください。

  • 環境変数を使用して、オペレーショナルパラメータを関数に渡します。たとえば、Amazon S3 バケットに書き込む場合、書き込み先のバケット名はハードコーディングせずに、環境変数として設定します。

  • Lambda 関数では、再帰呼び出しを使用しないでください。関数が自身を呼び出すこともあれば、新たに開始されたプロセスで関数が再度呼び出される可能性もあります。これを行うと意図しないボリュームで関数が呼び出され、料金が急増する可能性があります。意図しない呼び出しがいくつも見つかった場合は、すぐに関数の予約済同時実行数を 0 に設定して、コードを更新している間のすべての関数の呼び出しをスロットリングします。

  • Lambda 関数コードで文書化されていない非公開の API を使用しないでください。AWS Lambda マネージドランタイムでは、Lambda が Lambda の内部 API にセキュリティと機能面の更新を定期的に適用します。これらの内部 API 更新には後方互換性がないことがあり、関数にこれらの非公開 API に対する依存関係がある場合、呼び出しの失敗などの意図しない結果につながります。公開されている API のリストについては、「API リファレンス」を参照してください。

  • 冪等性コードを記述します。関数の記述に冪等性コードを使用すると、重複するイベントが同じ方法で処理されるようになります。コードでは、イベントを適切に検証し、重複するイベントを適切に処理する必要があります。詳細については、「Lambda 関数を冪等にするにはどうすればよいですか?」を参照してください。