How To Organize Golang Cloud Functions Code

serbixote

Marco Davalos

Posted on December 7, 2020

How To Organize Golang Cloud Functions Code

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
.
├── functions
│   ├── awesome
│   │   ├── awesome.go
│   │   ├── go.mod
│   │   └── go.sum
│   └── marvelous
│       ├── marvelous.go
│       ├── go.mod
│       └── go.sum
└── pkg
    ├── compliments
    │   └── compliments.go
    └── go.mod
Enter fullscreen mode Exit fullscreen mode
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)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

To avoid this we create the vendor directory before deploying:

go mod vendor
Enter fullscreen mode Exit fullscreen mode

and a .gcloudignore file, to use the vendor approach by not uploading the go.mod and go.sum files.

go.mod
go.sum
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
serbixote
Marco Davalos

Posted on December 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related