func init() vs func main() for initalizing global state in AWS Lambda handlers

huangapple go评论90阅读模式
英文:

func init() vs func main() for initalizing global state in AWS Lambda handlers

问题

在官方的AWS Lambda函数处理程序(Go语言)文档的使用全局状态部分中,建议在func init()中初始化所有全局状态。也就是说,我们想要在多个Lambda调用之间共享的任何包级变量都应该放在这里。我理解的是,这个初始化只在每个Lambda容器启动时(即冷启动时)执行一次。

我的问题是,是否可以使用func main()来代替func init()来实现相同的效果。使用func init()会导致我的处理程序函数(func LambdaHandler)由于func init()的副作用而无法进行单元测试。将func init()中的代码移到func main()似乎可以轻松解决这个问题。使用func main()func init()有什么副作用吗?

代码示例:

使用func init()

package main
 
import (
        "log"
        "github.com/aws/aws-lambda-go/lambda"
        "github.com/aws/aws-sdk-go/aws/session"
        "github.com/aws/aws-sdk-go/service/s3"
        "github.com/aws/aws-sdk-go/aws"
)
 
var invokeCount = 0
var myObjects []*s3.Object

func init() {
        svc := s3.New(session.New())
        input := &s3.ListObjectsV2Input{
                Bucket: aws.String("examplebucket"),
        }
        result, _ := svc.ListObjectsV2(input)
        myObjects = result.Contents
}
 
func LambdaHandler() (int, error) {
        invokeCount = invokeCount + 1
        log.Print(myObjects)
        return invokeCount, nil
}
 
func main() {
        lambda.Start(LambdaHandler)
}

使用func main()

package main
 
import (
        "log"
        "github.com/aws/aws-lambda-go/lambda"
        "github.com/aws/aws-sdk-go/aws/session"
        "github.com/aws/aws-sdk-go/service/s3"
        "github.com/aws/aws-sdk-go/aws"
)
 
var invokeCount = 0
var myObjects []*s3.Object
 
func LambdaHandler() (int, error) {
        invokeCount = invokeCount + 1
        log.Print(myObjects)
        return invokeCount, nil
}
 
func main() {
        svc := s3.New(session.New())
        input := &s3.ListObjectsV2Input{
                Bucket: aws.String("examplebucket"),
        }
        result, _ := svc.ListObjectsV2(input)
        myObjects = result.Contents

        lambda.Start(LambdaHandler)
}
英文:

Looking at the Using global state section in the official AWS Lambda function handler in Go doc
https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html

suggests to initialise all global state in func init()
i.e. Any package level vars which we want to share across multiple lambda invocations go here.
And my understanding is that this initialisation is done once per lambda container start (i.e cold start).

My question is, is it possible to do the same using func main() instead of func init().
Using func init() basically makes my handler function (func LambdaHandler) non unit-testable due to side-effects from func init() running.
Moving the func init() code to func main() seems to solve this easily.
Are there any side effects to using func main() vs func init()

Code Example

Using func init()

package main
 
import (
        "log"
        "github.com/aws/aws-lambda-go/lambda"
        "github.com/aws/aws-sdk-go/aws/session"
        "github.com/aws/aws-sdk-go/service/s3"
        "github.com/aws/aws-sdk-go/aws"
)
 
var invokeCount = 0
var myObjects []*s3.Object
func init() {
        svc := s3.New(session.New())
        input := &s3.ListObjectsV2Input{
                Bucket: aws.String("examplebucket"),
        }
        result, _ := svc.ListObjectsV2(input)
        myObjects = result.Contents
}
 
func LambdaHandler() (int, error) {
        invokeCount = invokeCount + 1
        log.Print(myObjects)
        return invokeCount, nil
}
 
func main() {
        lambda.Start(LambdaHandler)
}

vs

Using func main()

package main
 
import (
        "log"
        "github.com/aws/aws-lambda-go/lambda"
        "github.com/aws/aws-sdk-go/aws/session"
        "github.com/aws/aws-sdk-go/service/s3"
        "github.com/aws/aws-sdk-go/aws"
)
 
var invokeCount = 0
var myObjects []*s3.Object
 
func LambdaHandler() (int, error) {
        invokeCount = invokeCount + 1
        log.Print(myObjects)
        return invokeCount, nil
}
 
func main() {
        svc := s3.New(session.New())
        input := &s3.ListObjectsV2Input{
                Bucket: aws.String("examplebucket"),
        }
        result, _ := svc.ListObjectsV2(input)
        myObjects = result.Contents

        lambda.Start(LambdaHandler)
}

答案1

得分: 10

我建议使用以下代码(我们在许多 Go Lambda 中都使用成功)。

main.go

[...]

func (h *handler) handleRequest(ctx context.Context) error {
	input := h.s3Client.ListObjectsV2Input{
        Bucket: aws.String("examplebucket"),
    }

    [...]
}

type handler struct {
	s3Client s3iface.S3API
}

// main 函数只在 Lambda 初始化时调用一次(第一次启动时)。此函数中的代码主要用于创建服务客户端、读取环境变量、从磁盘读取配置等。
func main() {
	h := handler{
		s3Client: s3.New(session.New()),
	}

	lambda.Start(h.handleRequest)
}

main_test.go

type ListObjectsV2Mock struct {
	s3iface.S3API

	output *s3.ListObjectsV2Output
}

func TestHandleRequest(t *testing.T) {
	h := handler{
		s3Client: &ListObjectsV2Mock{
			output: &s3.ListObjectsV2Output{...},
		},
	}

	err := h.HandleRequest(context.TODO())
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

显然,缺少了很多代码(导入、错误处理等),但这是主要内容。

英文:

I would propose the following (which we use successful in a lot of Go Lambdas).

main.go

[...]

func (h *handler) handleRequest(ctx context.Context) error {
	input := h.s3Client.ListObjectsV2Input{
        Bucket: aws.String("examplebucket"),
    }

    [...]
}

type handler struct {
    s3Client s3iface.S3API
}

// main is called only once, when the Lambda is initialised (started for the first time). Code in this function should
// primarily be used to create service clients, read environments variables, read configuration from disk etc.
func main() {
	h := handler{
		s3client: s3.New(session.New()),
	}

	lambda.Start(h.handleRequest)
}

main_test.go

type ListObjectsV2Mock struct {
	s3iface.S3API

	output *s3.ListObjectsV2Output
}

func TestHandleRequest(t *testing.T) {
	h := handler{
		s3Client: &ListObjectsV2Mock{
			output: &s3.ListObjectsV2Output{...},
		},
	}

	err := h.HandleRequest(context.TODO())
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

Obviously, a lot of code is missing (imports, error handling etc), but this is the gist of it.

huangapple
  • 本文由 发表于 2021年6月3日 23:35:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/67824489.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定