how.wtf

Golang AWS Lambda project structure

· Thomas Taylor

how to structure a Golang AWS Lambda project

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}

#Go  

Reply to this post by email ↪