Go の Lambda 関数ハンドラーの定義
Lambda 関数ハンドラーは、イベントを処理する関数コード内のメソッドです。関数が呼び出されると、Lambda はハンドラーメソッドを実行します。関数は、ハンドラーが応答を返すか、終了するか、タイムアウトするまで実行されます。
このページでは、プロジェクトのセットアップ、命名規則、ベストプラクティスなど、Go で Lambda 関数ハンドラーを操作する方法について説明します。このページには、注文に関する情報を取得し、テキストファイル受信を生成し、このファイルを Amazon Simple Storage Service (S3) バケットに配置する Go Lambda 関数の例も含まれています。関数を書き込んだ後にデプロイする方法については、「.zip ファイルアーカイブを使用して Go Lambda 関数をデプロイする」または「コンテナイメージを使用して Go Lambda 関数をデプロイする」を参照してください。
トピック
Go ハンドラープロジェクトのセットアップ
Gogo mod init
コマンドを使用して他の Go プロジェクトを初期化するのと同じ方法で初期化できます:
go mod init
example-go
ここでは、example-go
がモジュール名です。これは何にでも置き換えることができます。このコマンドはプロジェクトを初期化し、プロジェクトの依存関係を一覧表示する go.mod
ファイルを生成します。
go get
コマンドを使用して、外部依存関係をプロジェクトに追加します。例えば、Go のすべての Lambda 関数には、Go 用の Lambda プログラミングモデルを実装する github.com/aws/aws-lambda-go/lambdago get
コマンドでこのパッケージを含めます:
go get github.com/aws/aws-lambda-go
関数コードは Go ファイルに存在する必要があります。以下の例では、この main.go
ファイルに名前を付けます。このファイルではハンドラーメソッドに、このハンドラーを呼び出す main()
関数と同様にコア関数ロジックを実装します。
Go Lambda 関数のコードの例
以下の Go Lambda 関数コードの例では、注文に関する情報を取得し、テキストファイル受信を生成し、このファイルを Amazon S3 バケットに配置します。
例 main.go
Lambda 関数
package main import ( "context" "encoding/json" "fmt" "log" "os" "strings" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" ) type Order struct { OrderID string `json:"order_id"` Amount float64 `json:"amount"` Item string `json:"item"` } var ( s3Client *s3.Client ) func init() { // Initialize the S3 client outside of the handler, during the init phase cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Fatalf("unable to load SDK config, %v", err) } s3Client = s3.NewFromConfig(cfg) } func uploadReceiptToS3(ctx context.Context, bucketName, key, receiptContent string) error { _, err := s3Client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucketName, Key: &key, Body: strings.NewReader(receiptContent), }) if err != nil { log.Printf("Failed to upload receipt to S3: %v", err) return err } return nil } func handleRequest(ctx context.Context, event json.RawMessage) error { // Parse the input event var order Order if err := json.Unmarshal(event, &order); err != nil { log.Printf("Failed to unmarshal event: %v", err) return err } // Access environment variables bucketName := os.Getenv("RECEIPT_BUCKET") if bucketName == "" { log.Printf("RECEIPT_BUCKET environment variable is not set") return fmt.Errorf("missing required environment variable RECEIPT_BUCKET") } // Create the receipt content and key destination receiptContent := fmt.Sprintf("OrderID: %s\nAmount: $%.2f\nItem: %s", order.OrderID, order.Amount, order.Item) key := "receipts/" + order.OrderID + ".txt" // Upload the receipt to S3 using the helper method if err := uploadReceiptToS3(ctx, bucketName, key, receiptContent); err != nil { return err } log.Printf("Successfully processed order %s and stored receipt in S3 bucket %s", order.OrderID, bucketName) return nil } func main() { lambda.Start(handleRequest) }
この main.go
ファイルには以下のコードのセクションが含まれます:
-
package main
: Go では、func main()
関数を含むパッケージは常にmain
と名付けられる必要があります。 -
import
ブロック: このブロックを使用して、Lambda 関数に必要なライブラリを含めます。 -
type Order struct {}
ブロック: この Go 構造体で予想される入力イベントの形状を定義します。 -
var ()
ブロック: このブロックを使用して、Lambda 関数で使用するグローバル変数を定義します。 -
func init() {}
: このinit()
メソッドには、初期化フェーズ 中に Lambda を実行するコードを含めます。 -
func uploadReceiptToS3(...) {}
: これは、メインhandleRequest
ハンドラーメソッドによって参照されるヘルパーメソッドです。 -
func handleRequest(ctx context.Context, event json.RawMessage) error {}
: これはメインハンドラーメソッドで、メインアプリケーションロジックが含まれています。 -
func main() {}
: これは Lambda ハンドラーに必要なエントリポイントです。lambda.Start()
メソッドの引数は、メインハンドラーメソッドです。
この関数が正しく機能するには、実行ロールで s3:PutObject
アクションを許可する必要があります。また、必ず RECEIPT_BUCKET
環境変数を定義してください。呼び出しに成功したら、Amazon S3 バケットに受信ファイルが含まれているはずです。
ハンドラーの命名規則
Go の Lambda 関数では、ハンドラーに任意の名前を使用できます。この例では、ハンドラーメソッド名は handleRequest
です。コード内でハンドラー値を参照するには、_HANDLER
環境変数を使用できます。
.zip デプロイパッケージを使用してデプロイされた Go 関数の場合、関数コードを含む実行ファイルには bootstrap
という名前を付ける必要があります。さらに、bootstrap
ファイルは、.zip ファイルのルートに置く必要があります。コンテナイメージを使用してデプロイされた Go 関数の場合、実行ファイルには任意の名前を使用できます。
入力イベントオブジェクトの定義とアクセス
JSON は Lambda 関数の最も一般的な標準入力形式です。この例では、関数は以下のような入力を想定しています:
{ "order_id": "12345", "amount": 199.99, "item": "Wireless Headphones" }
Go で Lambda 関数を使用する場合、予想される入力イベントの形状を Go 構造体として定義できます。この例では、Order
を表す構造体を定義します。
type Order struct { OrderID string `json:"order_id"` Amount float64 `json:"amount"` Item string `json:"item"` }
この構造体は、予想される入力形状と一致します。構造体を定義したら、encoding/json 標準ライブラリ
func handleRequest(ctx context.Context, event json.RawMessage) error { // Parse the input event var order Order if err := json.Unmarshal(event, &order); err != nil { log.Printf("Failed to unmarshal event: %v", err) return err ... }
この逆シリアル化の後、order
変数のフィールドにアクセスできます。例えば、order.OrderID
は元の入力から "order_id"
の値を取得します。
注記
encoding/json
パッケージはエクスポートされたフィールドにのみアクセスできます。エクスポートするには、イベント構造体のフィールド名が大文字である必要があります。
Lambda コンテキストオブジェクトへのアクセスと使用
Lambda コンテキストオブジェクトには、呼び出し、関数、および実行環境に関する情報が含まれます。この例では、ハンドラー署名でこの変数を ctx
として宣言しました:
func handleRequest(ctx context.Context, event json.RawMessage) error { ... }
ctx context.Context
入力は、関数ハンドラーのオプション引数です。受理されたハンドラー署名の詳細については、「Go ハンドラーの有効なハンドラー署名」を参照してください。
AWS SDK を使用して他のサービスを呼び出す場合、コンテキストオブジェクトはいくつかの重要な領域で必要です。例えば、SDK クライアントを適切に初期化するには、以下のようにコンテキストオブジェクトを使用して正しい AWS SDK 設定をロードできます:
// Load AWS SDK configuration using the default credential provider chain cfg, err := config.LoadDefaultConfig(ctx)
SDK 呼び出し自体では、コンテキストオブジェクトを入力として必要とする場合があります。例えば、s3Client.PutObject
呼び出しはコンテキストオブジェクトを最初の引数として受け入れます:
// Upload the receipt to S3 _, err = s3Client.PutObject(ctx, &s3.PutObjectInput{ ... })
AWS SDK リクエスト以外では、関数のモニタリングにコンテキストオブジェクトを使用することもできます。コンテキストオブジェクトの詳細については、「Lambda コンテキストオブジェクトを使用して Go 関数の情報を取得する」を参照してください。
Go ハンドラーの有効なハンドラー署名
Go で Lambda 関数ハンドラーを構築するには複数の方法がありますが、次のルールに従う必要があります。
-
ハンドラーは関数である必要があります。
-
ハンドラーは 0 から 2 までの引数を取る場合があります。2 つの引数がある場合は、最初の引数が
context.Context
を実装する必要があります。 -
ハンドラーは 0 から 2 までの引数を返す場合があります。単一の戻り値がある場合は、この値が
error
を実装する必要があります。2 つの戻り値がある場合には、2 番目の値がerror
を実装している必要があります。
次のリストは、有効なハンドラー署名の一覧です。 TIn
と TOut
は、 encoding/json 標準ライブラリと互換性のあるタイプを表しています。詳細については、「func アンマーシャリング
-
func ()
-
func () error
-
func () (TOut, error)
-
func (TIn) error
-
func (TIn) (TOut, error)
-
func (context.Context) error
-
func (context.Context) (TOut, error)
-
func (context.Context, TIn) error
-
func (context.Context, TIn) (TOut, error)
ハンドラーでの AWS SDK for Go v2 の使用
多くの場合、Lambda 関数を使用して、他の AWS リソースとやり取りしたり、更新したりします。これらのリソースとインターフェイスする最も簡単な方法は、AWS SDK for Go v2 を使用することです。
注記
AWS SDK for Go (v1) はメンテナンスモードであり、2025 年 7 月 31 日にサポートが終了する予定です。今後は AWS SDK for Go v2 のみを使用することをお勧めします。
SDK 依存関係を関数に追加するには、必要な特定の SDK クライアントに go get
コマンドを使用します。前のサンプルコードでは、config
ライブラリと s3
ライブラリを使用しました。これらの依存関係を追加するには、go.mod
と main.go
ファイルが格納されているディレクトリで以下のコマンドを実行します。
go get github.com/aws/aws-sdk-go-v2/config go get github.com/aws/aws-sdk-go-v2/service/s3
次に、依存関係を関数のインポートブロックにインポートします:
import ( ... "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" )
ハンドラーで SDK を使用する場合は、適切な設定でクライアントを設定します。これを行う最も簡単な方法は、デフォルトの認証情報プロバイダーチェーンを使用することです。この例では、この設定をロードする 1 つの方法を示しています:
// Load AWS SDK configuration using the default credential provider chain cfg, err := config.LoadDefaultConfig(ctx) if err != nil { log.Printf("Failed to load AWS SDK config: %v", err) return err }
この設定を cfg
変数にロードした後、この変数をクライアントインスタンスに渡すことができます。サンプルコードは、以下のように Amazon S3 クライアントをインスタンス化します:
// Create an S3 client s3Client := s3.NewFromConfig(cfg)
この例では、関数を呼び出すたびに初期化する必要がないように、init()
関数で Amazon S3 クライアントを初期化しました。問題は、init()
関数で Lambda がコンテキストオブジェクトにアクセスできないことです。回避策として、初期化フェーズ中に context.TODO()
のようなプレースホルダーを渡すことができます。後で、クライアントを使用して呼び出しを行うときは、コンテキストオブジェクト全体を渡します。この回避策については、AWS SDK クライアントの初期化と呼び出しでのコンテキストの使用 でも説明されています。
SDK クライアントを設定して初期化したら、SDK クライアントを使用して他の AWS サービスとやり取りできます。サンプルコードは、以下のように Amazon S3 PutObject
API を呼び出します。
_, err = s3Client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucketName, Key: &key, Body: strings.NewReader(receiptContent), })
環境変数にアクセスする
ハンドラーコードでは、os.Getenv()
メソッドを使用して任意の環境変数を参照できます。この例では、以下のコード行を使用して、定義された RECEIPT_BUCKET
環境変数を参照します:
// Access environment variables bucketName := os.Getenv("RECEIPT_BUCKET") if bucketName == "" { log.Printf("RECEIPT_BUCKET environment variable is not set") return fmt.Errorf("missing required environment variable RECEIPT_BUCKET") }
グローバルな状態を使用する
関数を呼び出すたびに新しいリソースが作成されないようにするには、Lambda 関数のハンドラーコードの外部でグローバル変数を宣言および変更できます。これらのグローバル変数は、var
ブロックまたはステートメントで定義します。さらに、ハンドラーは初期化フェーズ中に実行される init()
関数を宣言することがあります。この init
メソッドは、AWS Lambda でも標準の Go プログラムと同じように作動します。
Lambda 関数のコードのベストプラクティス
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 関数を冪等にするにはどうすればよいですか?
」を参照してください。