How To Organize Golang Cloud Functions Code
Marco Davalos
Posted on December 7, 2020
On our journey to build the next billion dollar app, we decided to use Cloud Functions to write the backend logic in Golang. So thinking about how the hell we were gonna organize the code, was one of the first things we went through.
The only thing we knew for sure, was that we wanted all code in a single repository, in order to speed up the development velocity.
This is what we came up with:
A Go module per function
.
└── functions
├── awesome
│ ├── awesome.go
│ ├── go.mod
│ └── go.sum
└── marvelous
├── marvelous.go
├── go.mod
└── go.sum
Having a Go module per function enables every function to manage its dependencies independently.
This avoids annoyances like making other functions stick to an older package version, or breaking other functions by upgrading major versions.
Relative imports
However, there's shared logic between functions that needs to be in common packages, and not having the functions within the same module makes it tricky to use them from the functions code.
Here's where the replace
directive comes to the rescue, allowing us point an import path to another module located in VCS (GitHub or elsewhere), or on the local filesystem with a relative or absolute file path.
module github.com/example/functions/marvelous
go 1.13
replace github.com/example/functions/pkg => ../../pkg
.
├── functions
│ ├── awesome
│ │ ├── awesome.go
│ │ ├── go.mod
│ │ └── go.sum
│ └── marvelous
│ ├── marvelous.go
│ ├── go.mod
│ └── go.sum
└── pkg
├── compliments
│ └── compliments.go
└── go.mod
package marvelous
import (
"fmt"
"net/http"
"github.com/example/compliments"
)
func CallMeMarvelous(w http.ResponseWriter, r *http.Request) {
text := compliments.Say("marvelous")
fmt.Fprint(w, text)
}
Vendor directory
Even though for development purposes this will work just fine, when the time to deploy the function comes, this won't cut it.
Executing gcloud functions deploy
from the function directory, won't upload the pkg
directory since it's located in a higher level, causing the above error:
OperationError: code=13, message=Build failed: go: github.com/example/functions/pkg@v0.0.0-00010101000000-000000000000: parsing /pkg/go.mod: open /pkg/go.mod: no such file or directory; Error ID: 03a1e2f7
To avoid this we create the vendor directory before deploying:
go mod vendor
and a .gcloudignore
file, to use the vendor approach by not uploading the go.mod
and go.sum
files.
go.mod
go.sum
Now all the function needs is to be built and run is within the directory. Additionally the **/vendor
path can be added to the root .gitignore
.
Conclusion
We are not Golang experts and it might not be a perfect approach, but it's the most elegant we found so far.
Posted on December 7, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.