AWS SAM is a great way to deploy web apps through infrastructure as code (IAC). I recently tried to use it for a project in my job, but I stumble with a harsh reality...
Go is the Ugly Duckling of AWS ๐ฆ
The chapter dedicated to Go in AWS SAM documentation, is really short and vague and suggest to REPEAT OUR SOURCE CODE A LOT! Having one go.mod
, go.sum
and utility functions for each lambda ๐บ!
I wrote this post for you, someone whoโs probably just as lost as I was ๐ตโ๐ซ. Letโs figure this out together!
This will be a 2 parts series:
- File Structure to NOT REPEAT CODE (This post)
- Custimize builds with Makefiles and Dockerfiles
Some Context on Go Runtimes
At the current time, Go runtime for lambdas is not supported as-is. This means that there is not specific option on AWS lambdas to specify that your code is written in Go. Instead, AWS provides 2 generic runtimes ๐ง:
-
al2
(Amazon Linux 2) -
al2023
(Amazon Linux 2023)
Which refers to the OS in which the lambda will be run. Is recommended to use al2023 since is newer and compatible with AWS Graviton processors which deliver better performance for a better price.
Anyway, this runtimes asks us to provide an executable (usually named bootstrap
), which will be executed on every lambda function. So instead of deliver code to the lambdas, we deliver an executable that we compiled with Go previously. Simple right?
This also removes the necessity of using lambda layers, that languages like JS need, since all common dependencies will already be packed in the compiled executable ๐ผ.
The problem
So, how we build that executable? AWS suggest us that every lambda should be stored on a folder along with its go.mod
and go.sum
, the template they provides us looks like this:
.
โโโ hello-world/
โ โโโ go.mod
โ โโโ go.sum
โ โโโ main.go
โโโ events/
โ โโโ ...
โโโ samconfig.toml
โโโ template.yaml
And this is the function definition on template.yaml
HelloWorldFunction:
Type: AWS::Serverless::Function
Metadata:
BuildMethod: go1.x
Properties:
CodeUri: hello-world/
Handler: bootstrap
Runtime: provided.al2023
Architectures:
- x86_64
Events:
CatchAll:
Type: Api
Properties:
Path: /hello
Method: GET
If we look the Lambda definition, we learn that
-
BuildMethod: go1.x
We use AWS built-it Go builder, to build the executable for us -
CodeUri: hello-world/
The lambda code will exclusively live in this directory. -
Handler: bootstrap
The name of the executable will bebootstrap
-
Runtime: provided.al2023
this will be the runtime.
Do you see the problem? At the moment we need a second lambda, we have to create a A NEW DIRECTORY WITH ITS OWN go.mod
, go.sum
and dependencies
, ยฟWhat if we want to share a utility function between to lambdas? TO BAAD ๐ฟ !you will have to copy the same file in the new lambda folder. Leaving with a file structure that looks like this:
.
โโโ function1/
โ โโโ go.mod
โ โโโ go.sum
โ โโโ main.go
โ โโโ SHAREDFUNC.go
โโโ function2/
โ โโโ go.mod
โ โโโ go.sum
โ โโโ main.go
โ โโโ SHAREDFUNC.go
โโโ events/
โ โโโ ...
โโโ samconfig.toml
โโโ template.yaml
This is gross ๐คฎ! There is a lot of repeated code! And it will become worst the more lambdas we add. There must be a better way!
The Solution
Since I wanted to share go.mod
, go.sum
and utility code through all lambdas, I came up with this structure:
.
โโโ cmd/
โ โโโ function1/
โ โ โโโ function1.go # contains main()
โ โโโ function2/
โ โโโ function2.go # contains main()
โโโ internal/
โ โโโ SHAREDFUNC.go
โโโ events/
โ โโโ ...
โโโ go.mod
โโโ go.sum
โโโ samconfig.toml
โโโ template.yaml
- I factored out all the common code on an
internal/
folder, - Place the
go.mod
andgo.sum
files in the root dir - Move all lambda entry points to
/cmd
(in Go exist this convention that whenever a project produces more than one executable, the entry points are placed within acmd
dir)
Now I just had to notify AWS SAM of this new structure ๐ธ! And I found the solution just by tweaking around the values of CodeUri
and Handler
.
The Secret ๐
Seems that if you
- Move the
go.mod
andgo.sum
to the root folder - Set
CodeUri
, to wherever folder your function entry point is.
SAM will detect it automatically and build with root dependencies and internal/
code ๐ ๐ ๐
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Metadata:
BuildMethod: go1.x
Properties:
CodeUri: ./cmd/function1/
Handler: bootstrap
Runtime: provided.al2023
... # More properties ...
HelloWorldFunction:
Type: AWS::Serverless::Function
Metadata:
BuildMethod: go1.x
Properties:
CodeUri: ./cmd/function2
Handler: bootstrap
Runtime: provided.al2023
... # More properties ...
Can it be better?
Yeeees โจ, we will discuss more ways to customize your Go compilation in the next post!
Top comments (0)