

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 定義 Go 格式的 Lambda 函數處理常式
<a name="golang-handler"></a>

Lambda 函數*處理常式*是您的函數程式碼中處理事件的方法。當有人呼叫您的函數時，Lambda 會執行處理常式方法。函數會執行，直到處理常式傳回回應、結束或逾時為止。

此頁面說明如何使用 Go 處理 Lambda 函數處理常式，包括專案設定、命名慣例和最佳實務。本頁還提供了一個 Go Lambda 函式範例，該函式會取得訂單的相關資訊、產生文字檔案收據，並將該檔案放入 Amazon Simple Storage Service (Amazon S3) 儲存貯體。如需編寫函數後如何部署函數的詳細資訊，請參閱[使用 .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)
+ [在處理常式中使用 適用於 Go 的 AWS SDK v2](#golang-example-sdk-usage)
+ [存取環境變數](#golang-example-envvars)
+ [使用全域狀態](#golang-handler-state)
+ [Go Lambda 函數的程式碼最佳實務](#go-best-practices)

## 設定 Go 處理常式專案
<a name="golang-handler-setup"></a>

以 [Go](https://golang.org/) 撰寫的 Lambda 函數被製作成 Go 可執行檔。與初始化任何其他 Go 專案相同，您可以使用以下 `go mod init` 命令初始化一個 Go Lambda 函數專案：

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

此處 `example-go` 是模組名稱。您可以將其取代為任何值。此命令會初始化專案，並產生列有專案相依項的 `go.mod` 檔案。

使用 `go get` 命令將任何外部相依項新增至專案。例如，對於以 Go 編寫的所有 Lambda 函數程式碼，您需要納入 [github.com/aws/aws-lambda-go/lambda](https://github.com/aws/aws-lambda-go/tree/master/lambda) 套件，它可針對 Go 實作 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 struct 中預期輸入事件的形狀。
+ `var ()` 區塊：用於定義將在 Lambda 函數中使用的任何全域變數。
+ `func init() {}`：在此 `init()` 方法的中包含您希望 Lambda 在[初始化階段](lambda-runtime-environment.md#runtimes-lifecycle-ib)執行的任何程式碼。
+ `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 struct。在此範例中，我們透過定義 struct 來代表 `Order`：

```
type Order struct {
    OrderID string  `json:"order_id"`
    Amount  float64 `json:"amount"`
    Item    string  `json:"item"`
}
```

此 struct 符合預期的輸入形狀。定義 struct 之後，可以撰寫處理常式簽章，該簽章採用與 [encoding/json standard library](https://pkg.go.dev/encoding/json) 相容的一般 JSON 類型。然後，可以使用 [func Unmarshal](https://golang.org/pkg/encoding/json/#Unmarshal) 函數將其還原序列化至該 struct。這在處理常式的前幾行中得以曾現：

```
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_id"` 可以從原始輸入擷取 `order.OrderID` 的值。

**注意**  
`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 呼叫其他服務，則有幾個關鍵區域需要內容物件。例如，您可以使用內容物件載入正確的 AWS SDK 組態，以正確初始化 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 之間的引數。如果有兩個引數，第一個引數必須實作 `context.Context`。
+ 處理常式傳回 0 到 2 之間的引數。若有單一傳回值，它必須實作 `error`。如果有兩個傳回值，第二個值必須實作 `error`。

下方將列出有效的處理常式簽章。`TIn` 和 `TOut` 皆代表與 *encoding/json* 標準程式庫相容的類型。如需詳細資訊，請參閱 [func Unmarshal](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)
  ```

## 在處理常式中使用 適用於 Go 的 AWS SDK v2
<a name="golang-example-sdk-usage"></a>

通常，您會使用 Lambda 函數與其他 AWS 資源互動或進行更新。與這些資源互動的最簡單方法便是使用 [適用於 Go 的 AWS SDK v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2)。

**注意**  
適用於 Go 的 AWS SDK (v1) 目前處於維護模式，並且將於 2025 年 7 月 31 日終止支援。我們建議您在之後僅使用 適用於 Go 的 AWS SDK 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)。此範例說明了載入此組態的其中一種方式：

```
// 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 用戶端之後，您就可以使用它與其他 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()` 函數。在 AWS Lambda 中，`init` 方法的行為與標準的 Go 程式相同。

## 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 參考](https://docs.aws.amazon.com/lambda/latest/api/welcome.html)查看公開可用 API 的清單。

**撰寫等冪程式碼。**為函數撰寫等冪程式碼可確保採用相同方式來處理重複事件。程式碼應正確驗證事件並正常處理重複的事件。如需詳細資訊，請參閱 [How do I make my Lambda function idempotent?](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-idempotent/) (如何讓 Lambda 函數等冪？)。