Go · Golang · Modules · Workspaces

Go Workspaces

I’ve been relatively conservative when it comes to Go modules, as they came on to the scene there was the normal process where the change wasn’t 100% meeting expectations. One of those changes was being able to work on local code and use it, without the build process downloading the published version that did not (yet) have the changes.

The initial solution was to use the replace directive in a go.mod, but this meant that developers could (and would) accidentally push go.mod files to their repo, which would break production builds because the filesystem on prod needed to match what the developer locally in order for the replace to work.

Example of replace being used in go.mod

module github.com/shanehowearth/cloudblog/REST

go 1.18

require (
        github.com/go-chi/chi v4.0.2+incompatible
        github.com/go-chi/render v1.0.1
        github.com/shanehowearth/cloudblog/readarticle v1.25.1
        golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect
)


replace github.com/shanehowearth/cloudblog/readarticle => ../readarticle

Another solution was to vendor versions of the local code so that the production build system would build with the vendored versions, instead of published versions.

Obviously these are hacks, and are brittle.

I’ve recently been working with a multi-repo/module system, a set of microservices that combine to create a backend system. One of the microservices provides a set of common functions for access to one of the datastore types in use. I created a new function for access, because changing the old function would have broken backward compatibility for a number of downstream, dependent microservices.

In order to make use of that new function in one (or more) of the downstream services, I would normally have to push/publish the (untested) code to our repo (so that the build/test applications locally would fetch the updated service), or use areplace directive in my go.mod.

Enter Go workspaces. Workspaces provide an easy way for developers to use local changes to other repos in tests. Incredibly easy.My first read of the tutorial (that I linked to earlier) left me confused. So that’s why I am writing this, because workspaces make life super easy.

The steps are as follows- we’re going to assume that, like I was, you are dealing with established codebase(s) that make use of go modules.

  1. Initialise the workspace (this creates a go.work file, and a go.sum file)
$ cd $REPO
$ go work init
  1. Edit the go.work file to tell go what local repos to use example go.work
go 1.18

use ./
use ../readarticle

That’s it, that’s everything you need to do.

For an explanation of the lines go 1.18 is the minimum Go version to use use ./ means to use the code (relative to this go.work) in this directory use ../readarticle means to use the code found in ../readarticle (again the path is relative to this go.work file location)

The build/test tools know from the path, and module name, what module is being replaced, and does so.

There’s no messy version numbers (that I have seen used in replace directives) there’s no chance of accidentally pushing a bad go.mod, and, because of the way I have configured my git there’s no pushing of go.work to prod.

Be aware, though, that if you do push that file (or want to build a container locally) then you will have problems similar to pushing a bad go.mod.

The solution is to not track the file(s), put go.work into your global gitignore, mine is at ~/.gitignore, and I have configured git to use that with

git config --global core.excludesFile ~/.gitignore

You could also put rules into your Dockerfile, or docker-compose.yml or build scripts, but I leave that as an exercise for the more knowledgeable :-)

Summary

Using Go workspaces is a dream compared to all the hangnails I used to deal with with replace directives (or vendoring), so get on the workspace train, and make useof them yourself.

Published:
comments powered by Disqus