Golang AWS Lambda project structure
AWS Lambda is an easy-to-use serverless offering that enables developers to quickly deploy code without worrying about maintenance, orchestration, scaling, etc. It’s simple to get started and its free tier is generous!
Golang project structure
GO does not have a recommended project structure. In fact, keeping a project structure flat is preferrable for small apps:
1project/
2├── go.mod
3├── go.sum
4├── user.go
5└── main.go
My personal preference is to use the flat structure for small apps or a combination of internal
+ cmd
:
1project/
2├── cmd
3│ └── app1
4│ └── main.go
5├── go.mod
6├── go.sum
7└── internal
8 └── user.go
What is the internal module
The internal
module can only be imported from packages rooted at the parent of the internal folder. In other words, the internal
folder is “private” or non-existent outside the package. It reduces API surface area and establishes a clear domain boundary that is enforced by GO.
What is the cmd module
The cmd
directory is a common convention I prefer for organizing sub-apps into runnable binaries. For example, a larger project may have multiple “apps” required to run it:
1store/
2├── cmd
3│ ├── api
4│ │ └── main.go
5│ ├── frontend
6│ │ └── main.go
7│ └── worker
8│ └── main.go
9├── go.mod
10├── go.sum
11└── internal
12 ├── cart.go
13 ├── product.go
14 └── user.go
Each “app” within the cmd
directory will have a distributable binary that can be invoked with required / optional flags. For example, the frontend
binary may have a -port
flag. The intention of the main.go
files are to be THIN entry points that delegate the heavy processing to the internal
layer.
It’s common for a simple main.go
to look like this:
1package main
2
3import (
4 "github.com/user/repo/internal"
5)
6
7func main() {
8 c := internal.NewClient()
9 c.DoSomething()
10}
Golang AWS Lambda project structure
Following the prior convention, lambdas can be similary nested in the cmd
directory:
1project/
2├── cmd
3│ ├── function1
4│ │ └── main.go
5│ └── function2
6│ └── main.go
7├── go.mod
8├── go.sum
9└── internal
10 └── user.go
With very THIN main.go
entry handlers:
1package main
2
3import (
4 "github.com/aws/aws-lambda-go/lambda"
5
6 "github.com/user/repo/internal"
7)
8
9func HandleRequest() error {
10 c := internal.NewClient()
11 c.DoSomething()
12}
13
14func main() {
15 lambda.Start(HandleRequest)
16}
If cmd
doesn’t fit your use case, creating another folder is completely fine:
1project/
2├── lambda
3│ ├── function1
4│ │ └── main.go
5│ └── function2
6│ └── main.go
7├── go.mod
8├── go.sum
9└── internal
10 └── user.go
Even having separate cmd
and lambda
folders to distinguish between CLI-invocable apps and Lambda-invocable apps is fine.
The main point is keeping the main.go
entry points thin.
Golang AWS Lambda project structure with CDK
For my use-case, I kept the lambda handlers nested within the cmd
directoy. In addition, I created a deploy
app for the CDK that contains the stack-creation.
1project/
2├── cdk.json
3├── cmd
4│ ├── function1
5│ │ └── main.go
6│ └── infrastructure
7│ └── main.go
8├── go.mod
9├── go.sum
10└── internal
11 └── s3.go
The cdk.json
is simple:
1{
2 "app": "go mod download && go run cmd/deploy/main.go"
3}