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)
}
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.