

# Go の Lambda 関数ハンドラーの定義
<a name="golang-handler"></a>

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

このページでは、プロジェクトのセットアップ、命名規則、ベストプラクティスなど、Go で Lambda 関数ハンドラーを操作する方法について説明します。このページには、注文に関する情報を取得し、テキストファイル受信を生成し、このファイルを Amazon Simple Storage Service (Amazon S3) バケットに配置する Go Lambda 関数の例も含まれています。関数を書き込んだ後にデプロイする方法については、「[.zip ファイルアーカイブを使用して Go Lambda 関数をデプロイする](golang-package.md)」または「[コンテナイメージを使用して Go Lambda 関数をデプロイする](go-image.md)」を参照してください。

**Topics**
+ [Go ハンドラープロジェクトのセットアップ](#golang-handler-setup)
+ [Go Lambda 関数のコードの例](#golang-example-code)
+ [ハンドラーの命名規則](#golang-handler-naming)
+ [入力イベントオブジェクトの定義とアクセス](#golang-example-input)
+ [Lambda コンテキストオブジェクトへのアクセスと使用](#golang-example-context)
+ [Go ハンドラーの有効なハンドラー署名](#golang-handler-signatures)
+ [ハンドラーでの AWS SDK for Go v2 の使用](#golang-example-sdk-usage)
+ [環境変数にアクセスする](#golang-example-envvars)
+ [グローバルな状態を使用する](#golang-handler-state)
+ [Lambda 関数のコードのベストプラクティス](#go-best-practices)

## Go ハンドラープロジェクトのセットアップ
<a name="golang-handler-setup"></a>

[Go](https://golang.org/) で書き込まれた Lambda 関数は、Go 実行可能ファイルとして作成されます。Go Lambda 関数プロジェクトは、以下の `go mod init` コマンドを使用して他の Go プロジェクトを初期化するのと同じ方法で初期化できます:

```
go mod init example-go
```

ここでは、`example-go` がモジュール名です。これは何にでも置き換えることができます。このコマンドはプロジェクトを初期化し、プロジェクトの依存関係を一覧表示する `go.mod` ファイルを生成します。

`go get` コマンドを使用して、外部依存関係をプロジェクトに追加します。例えば、Go のすべての Lambda 関数には、Go 用の Lambda プログラミングモデルを実装する [github.com/aws/aws-lambda-go/lambda](https://github.com/aws/aws-lambda-go/tree/master/lambda) パッケージを含める必要があります。以下の `go get` コマンドでこのパッケージを含めます:

```
go get github.com/aws/aws-lambda-go
```

関数コードは Go ファイルに存在する必要があります。以下の例では、この `main.go` ファイルに名前を付けます。このファイルではハンドラーメソッドに、このハンドラーを呼び出す `main()` 関数と同様にコア関数ロジックを実装します。

## Go Lambda 関数のコードの例
<a name="golang-example-code"></a>

以下の Go Lambda 関数コードの例では、注文に関する情報を取得し、テキストファイル受信を生成し、このファイルを Amazon S3 バケットに配置します。

**Example `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-runtime-environment.md#runtimes-lifecycle-ib) 中に Lambda を実行するコードを含めます。
+ `func uploadReceiptToS3(...) {}`: これは、メイン `handleRequest` ハンドラーメソッドによって参照されるヘルパーメソッドです。
+ `func handleRequest(ctx context.Context, event json.RawMessage) error {}`: これは**メインハンドラーメソッド**で、メインアプリケーションロジックが含まれています。
+ `func main() {}`: これは Lambda ハンドラーに必要なエントリポイントです。`lambda.Start()` メソッドの引数は、メインハンドラーメソッドです。

この関数が正しく機能するには、[実行ロール](lambda-intro-execution-role.md)で `s3:PutObject` アクションを許可する必要があります。また、必ず `RECEIPT_BUCKET` 環境変数を定義してください。呼び出しに成功したら、Amazon S3 バケットに受信ファイルが含まれているはずです。

## ハンドラーの命名規則
<a name="golang-handler-naming"></a>

Go の Lambda 関数では、ハンドラーに任意の名前を使用できます。この例では、ハンドラーメソッド名は `handleRequest` です。コード内でハンドラー値を参照するには、`_HANDLER` 環境変数を使用できます。

[.zip デプロイパッケージ](golang-package.md)を使用してデプロイされた Go 関数の場合、関数コードを含む実行ファイルには `bootstrap` という名前を付ける必要があります。さらに、`bootstrap` ファイルは、.zip ファイルのルートに置く必要があります。[コンテナイメージ](go-image.md#go-image-provided)を使用してデプロイされた Go 関数の場合、実行ファイルには任意の名前を使用できます。

## 入力イベントオブジェクトの定義とアクセス
<a name="golang-example-input"></a>

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 標準ライブラリ](https://pkg.go.dev/encoding/json)と互換性のある汎用 JSON 型を取り込むハンドラー署名を記述できます。その後、[func Unmarshal](https://golang.org/pkg/encoding/json/#Unmarshal) 関数を使用して、構造体に逆シリアル化できます。これはハンドラーの最初の数行に示されています:

```
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 コンテキストオブジェクトへのアクセスと使用
<a name="golang-example-context"></a>

Lambda [コンテキストオブジェクト](golang-context.md)には、呼び出し、関数、および実行環境に関する情報が含まれます。この例では、ハンドラー署名でこの変数を `ctx` として宣言しました:

```
func handleRequest(ctx context.Context, event json.RawMessage) error {
    ...
}
```

`ctx context.Context` 入力は、関数ハンドラーのオプション引数です。受理されたハンドラー署名の詳細については、「[Go ハンドラーの有効なハンドラー署名](#golang-handler-signatures)」を参照してください。

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 関数の情報を取得する](golang-context.md)」を参照してください。

## Go ハンドラーの有効なハンドラー署名
<a name="golang-handler-signatures"></a>

Go で Lambda 関数ハンドラーを構築するには複数の方法がありますが、次のルールに従う必要があります。
+ ハンドラーは関数である必要があります。
+ ハンドラーは 0 から 2 までの引数を取る場合があります。2 つの引数がある場合は、最初の引数が `context.Context` を実装する必要があります。
+ ハンドラーは 0 から 2 までの引数を返す場合があります。単一の戻り値がある場合は、この値が `error` を実装する必要があります。2 つの戻り値がある場合には、2 番目の値が `error` を実装している必要があります。

次のリストは、有効なハンドラー署名の一覧です。 `TIn` と `TOut` は、 *encoding/json* 標準ライブラリと互換性のあるタイプを表しています。詳細については、「[func アンマーシャリング](https://golang.org/pkg/encoding/json/#Unmarshal)」でこれらのタイプが逆シリアル化する方法を参照してください。
+ 

  ```
  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 の使用
<a name="golang-example-sdk-usage"></a>

多くの場合、Lambda 関数を使用して、他の AWS リソースとやり取りしたり、更新したりします。これらのリソースとインターフェイスする最も簡単な方法は、 [AWS SDK for Go v2](https://pkg.go.dev/github.com/aws/aws-sdk-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 ブロックにインポートします:

```
import (
    ...
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)
```

ハンドラーで SDK を使用する場合は、適切な設定でクライアントを設定します。これを行う最も簡単な方法は、[デフォルトの認証情報プロバイダーチェーン](https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html#credentialProviderChain)を使用することです。この例では、この設定をロードする 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 クライアントの初期化と呼び出しでのコンテキストの使用](golang-context.md#golang-context-sdk) でも説明されています。

SDK クライアントを設定して初期化したら、SDK クライアントを使用して他の AWS サービスとやり取りできます。サンプルコードは、以下のように Amazon S3 `PutObject` API を呼び出します。

```
_, err = s3Client.PutObject(ctx, &s3.PutObjectInput{
    Bucket: &bucketName,
    Key:    &key,
    Body:   strings.NewReader(receiptContent),
})
```

## 環境変数にアクセスする
<a name="golang-example-envvars"></a>

ハンドラーコードでは、`os.Getenv()` メソッドを使用して任意の[環境変数](configuration-envvars.md)を参照できます。この例では、以下のコード行を使用して、定義された `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")
    }
```

## グローバルな状態を使用する
<a name="golang-handler-state"></a>

関数を呼び出すたびに新しいリソースが作成されないようにするには、Lambda 関数のハンドラーコードの外部でグローバル変数を宣言および変更できます。これらのグローバル変数は、`var` ブロックまたはステートメントで定義します。さらに、ハンドラーは[初期化フェーズ](lambda-runtime-environment.md#runtimes-lifecycle-ib)中に実行される `init()` 関数を宣言することがあります。この `init` メソッドは、AWS Lambda でも標準の Go プログラムと同じように作動します。

## Lambda 関数のコードのベストプラクティス
<a name="go-best-practices"></a>

Lambda 関数をビルドするときは、次のリストのガイドラインに従って、コーディングのベストプラクティスを実行してください。
+ **Lambda ハンドラーをコアロジックから分離します。**これにより、関数の単体テストが実行しやすくなります。
+ **依存関係の複雑さを最小限に抑えます。**フレームワークを単純化して、[実行環境](lambda-runtime-environment.md)起動時のロードを高速化します。
+ **デプロイパッケージをランタイムに必要な最小限のサイズにします。**これにより、呼び出しに先立ってデプロイパッケージをダウンロードして解凍する所要時間が短縮されます。

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

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

**keep-alive ディレクティブを使用して永続的な接続を維持します。**Lambda は、時間の経過とともにアイドル状態の接続を消去します。関数を呼び出すときにアイドル状態の接続を再利用しようとすると、接続エラーが発生します。永続的な接続を維持するには、ランタイムに関連付けられている keep-alive ディレクティブを使用します。例については、「[Node.js で Keep-alive を使用して接続を再利用する](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html)」を参照してください。

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

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

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

**冪等性コードを記述します。**関数の記述に冪等性コードを使用すると、重複するイベントが同じ方法で処理されるようになります。コードでは、イベントを適切に検証し、重複するイベントを適切に処理する必要があります。詳細については、「[Lambda 関数を冪等にするにはどうすればよいですか?](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-idempotent/)」を参照してください。