Go에서 Lambda 함수 핸들러 정의하기

Go에서 Lambda 함수 핸들러 정의하기

Lambda 함수 핸들러는 함수 코드에서 이벤트를 처리하는 메서드입니다. 함수가 호출되면 Lambda가 핸들러 메서드를 실행하며, 함수는 핸들러가 응답을 반환하거나, 종료되거나, 타임아웃될 때까지 실행됩니다. 이 문서는 Go에서 Lambda 함수 핸들러를 사용하는 방법(프로젝트 설정, 명명 규칙, 모범 사례)을 다루고, 주문 정보를 받아 텍스트 파일 영수증을 만들어 Amazon Simple Storage Service(S3) 버킷에 저장하는 Go Lambda 함수 예제를 제공합니다.

출처: AWS Lambda 개발자 안내서

본문

이 페이지의 내용은 다음과 같습니다.

  • Go 핸들러 프로젝트 설정
  • 예제 Go Lambda 함수 코드
  • 핸들러 명명 규칙
  • 입력 이벤트 객체 정의 및 접근
  • Lambda 컨텍스트 객체 접근 및 사용
  • Go 핸들러의 유효한 핸들러 시그니처
  • 핸들러에서 AWS SDK for Go v2 사용
  • 환경 변수 접근
  • 전역 상태(global state) 사용
  • Go Lambda 함수의 코드 모범 사례

Go 핸들러 프로젝트 설정

Go로 작성된 Lambda 함수는 Go 실행 파일(executable)로 작성됩니다. 다른 Go 프로젝트를 초기화하는 것과 같은 방식으로, 다음 go mod init 명령으로 Go Lambda 함수 프로젝트를 초기화할 수 있습니다.

go mod init example-go

여기서 example-go는 모듈 이름이며 원하는 것으로 바꿀 수 있습니다. 이 명령은 프로젝트를 초기화하고 프로젝트의 의존성을 나열하는 go.mod 파일을 생성합니다.

프로젝트에 외부 의존성을 추가하려면 go get 명령을 사용합니다. 예를 들어 모든 Go Lambda 함수에는 Go용 Lambda 프로그래밍 모델을 구현하는 github.com/aws/aws-lambda-go/lambda 패키지를 포함해야 합니다. 이 패키지는 다음 go get 명령으로 추가합니다.

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

함수 코드는 Go 파일에 있어야 합니다. 다음 예제에서는 이 파일 이름을 main.go로 정합니다. 이 파일에 핸들러 메서드에서 쓰는 핵심 함수 로직과, 이 핸들러를 호출하는 main() 함수를 구현합니다.

예제 Go Lambda 함수 코드

다음 예제 Go Lambda 함수 코드는 주문 정보를 받아 텍스트 파일 영수증을 만들고 이를 Amazon S3 버킷에 저장합니다.

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() {}: 초기화 단계에서 Lambda가 실행하길 원하는 코드를 이 init() 메서드에 넣습니다.
  • func uploadReceiptToS3(...) {}: 메인 handleRequest 핸들러 메서드가 참조하는 헬퍼 메서드입니다.
  • func handleRequest(ctx context.Context, event json.RawMessage) error {}: 메인 애플리케이션 로직을 담은 메인 핸들러 메서드입니다.
  • func main() {}: Lambda 핸들러에 필수인 진입점입니다. lambda.Start() 메서드의 인자는 메인 핸들러 메서드입니다.

이 함수가 올바르게 작동하려면 실행 역할(execution role)이 s3:PutObject 액션을 허용해야 합니다. 또한 RECEIPT_BUCKET 환경 변수를 정의했는지 확인하세요. 호출에 성공하면 Amazon S3 버킷에 영수증 파일이 들어 있어야 합니다.

핸들러 명명 규칙

Go Lambda 함수에서는 핸들러에 어떤 이름이든 사용할 수 있습니다. 이 예제에서 핸들러 메서드 이름은 handleRequest입니다. 코드에서 핸들러 값을 참조하려면 _HANDLER 환경 변수를 사용할 수 있습니다.

.zip 배포 패키지로 배포하는 Go 함수의 경우, 함수 코드를 담은 실행 파일 이름이 bootstrap이어야 하며 .zip 파일의 루트에 있어야 합니다. 컨테이너 이미지로 배포하는 Go 함수는 실행 파일 이름을 자유롭게 정할 수 있습니다.

입력 이벤트 객체 정의 및 접근

JSON은 Lambda 함수에서 가장 흔하고 표준적인 입력 형식입니다. 이 예제에서 함수는 다음과 같은 입력을 기대합니다.

{
    "order_id": "12345",
    "amount": 199.99,
    "item": "Wireless Headphones"
}

Go에서 Lambda 함수를 쓸 때는 기대하는 입력 이벤트의 형태를 Go struct로 정의할 수 있습니다. 이 예제에서는 Order를 나타내는 struct를 정의합니다.

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

이 struct는 기대하는 입력 형태와 일치합니다. struct를 정의한 뒤에는 encoding/json 표준 라이브러리와 호환되는 범용 JSON 타입을 받는 핸들러 시그니처를 작성하고, func 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.OrderID는 원래 입력에서 "order_id" 값을 가져옵니다.

encoding/json 패키지는 내보내진(exported) 필드에만 접근할 수 있습니다. 내보내려면 이벤트 struct의 필드 이름이 대문자로 시작해야 합니다.

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개의 인자를 받을 수 있습니다. 인자가 두 개라면 첫 번째 인자가 context.Context를 구현해야 합니다.
  • 핸들러는 0~2개의 값을 반환할 수 있습니다. 반환 값이 하나라면 error를 구현해야 하고, 두 개라면 두 번째 값이 error를 구현해야 합니다.

다음은 유효한 핸들러 시그니처 목록입니다. TIn과 TOut는 encoding/json 표준 라이브러리와 호환되는 타입을 나타냅니다. 자세한 내용은 func 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 사용

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 블록에 의존성을 알맞게 추가합니다.

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

핸들러에서 SDK를 쓸 때는 올바른 설정으로 클라이언트를 구성합니다. 가장 간단한 방법은 기본 자격 증명 공급자 체인(default credential provider chain)을 사용하는 것입니다. 이 예제는 이 구성을 로드하는 한 가지 방법을 보여줍니다.

// 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() 같은 자리 채움 값(placeholder)을 전달하고, 나중에 클라이언트로 호출할 때는 전체 컨텍스트 객체를 전달합니다. 이 우회 방법은 'AWS 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")
    }

전역 상태(Global state) 사용

함수를 호출할 때마다 새 리소스를 만들지 않으려면 Lambda 함수 핸들러 코드 밖에서 전역 변수를 선언하고 수정할 수 있습니다. 이 전역 변수는 var 블록이나 문에서 정의합니다. 또한 핸들러는 초기화 단계에서 실행되는 init() 함수를 선언할 수 있습니다. init 메서드는 AWS Lambda에서 표준 Go 프로그램과 동일하게 동작합니다.

Go Lambda 함수의 코드 모범 사례

Lambda 함수를 만들 때 다음 목록의 지침을 지켜 모범 코딩 사례를 따르세요.

  • Lambda 핸들러를 핵심 로직에서 분리하세요. 이렇게 하면 더 단위 테스트하기 쉬운 함수를 만들 수 있습니다.
  • 의존성의 복잡성을 최소화하세요. 실행 환경 시작 시 빠르게 로드되는 더 단순한 프레임워크를 선호합니다.
  • 배포 패키지 크기를 런타임에 필요한 만큼만 유지하세요. 이렇게 하면 호출 전에 배포 패키지를 다운로드·압축 해제하는 데 걸리는 시간이 줄어듭니다.
  • 실행 환경 재사용을 활용해 함수 성능을 개선하세요. SDK 클라이언트와 데이터베이스 연결을 함수 핸들러 밖에서 초기화하고, 정적 자산을 로컬 /tmp 디렉터리에 캐시하세요. 같은 함수 인스턴스가 처리하는 이후 호출은 이 리소스를 재사용할 수 있습니다. 함수 실행 시간을 줄여 비용을 절약합니다.
  • 호출 간 데이터 누출을 막으려면 실행 환경에 사용자 데이터, 이벤트, 보안상 의미가 있는 정보를 저장하지 마세요. 핸들러 내 메모리에 저장할 수 없는 변경 가능한 상태에 의존한다면 사용자별로 별도의 함수나 별도 버전의 함수를 만드는 것을 고려하세요.
  • keep-alive 지시문을 사용해 지속 연결을 유지하세요. Lambda는 시간이 지나면 유휴 연결을 정리합니다. 함수 호출 시 유휴 연결을 재사용하려 하면 연결 오류가 발생합니다. 지속 연결을 유지하려면 런타임과 연결된 keep-alive 지시문을 사용하세요. 예시는 'Node.js에서 Keep-Alive로 연결 재사용'을 참고하세요.
  • 환경 변수로 작동 파라미터를 함수에 전달하세요. 예를 들어 Amazon S3 버킷에 쓸 때 버킷 이름을 하드코딩하지 말고 환경 변수로 구성하세요.
  • Lambda 함수에서 재귀 호출을 피하세요. 함수가 자기 자신을 호출하거나 함수를 다시 호출할 프로세스를 시작하면 의도치 않은 호출량과 비용 증가가 발생할 수 있습니다. 의도치 않은 호출량이 보이면, 코드를 갱신하는 동안 즉시 함수 예약 동시성을 0으로 설정해 모든 호출을 제한하세요.
  • Lambda 함수 코드에서 문서화되지 않은 비공개 API를 사용하지 마세요. Lambda 관리형 런타임의 경우 Lambda는 Lambda 내부 API에 보안·기능 업데이트를 주기적으로 적용합니다. 이런 내부 API 업데이트는 하위 호환성이 깨질 수 있으며, 이러한 비공개 API에 의존하는 함수는 호출 실패 같은 의도치 않은 결과를 겪을 수 있습니다. 공개적으로 사용 가능한 API 목록은 API 레퍼런스를 참고하세요.
  • 멱등(idempotent) 코드를 작성하세요. 함수에 멱등 코드를 작성하면 중복 이벤트가 동일하게 처리됩니다. 코드는 이벤트를 올바르게 검증하고 중복 이벤트를 우아하게 처리해야 합니다. 자세한 내용은 '내 Lambda 함수를 멱등하게 만드는 방법'을 참고하세요.

더 알아보기 (Learn more)