Nasty refactorings with go.mod replace
At work I’ve been building a new program on top of an SDK that’s under very active development. After about 6 weeks without updating the version, the SDK had deleted some code I was using and had a ton of breaking changes. If I’d simply updated the library, my entire program would fail to build, and it also wouldn’t be clear how to get it back to a good state. Following in the spirit of Branch by Abstraction, I’d rather introduce the new code side-by-side with the old and incrementally migrate without breaking the program.
I work in Go, where our import statements look like this:
import (
github.com/grafana/grafana
github.com/joeblubaugh/lib
)
The import
statements use package names, and if the packages are URLs, then go build
will try and download modules by doing a git clone
based on the package’s URL. Go’s dependency management uses require
statements to describe the dependency tree:
require github.com/joeblubaugh/lib v0.1.0
So, let’s say I need to update from v0.1.0
to v0.5.0
, but I need to keep the v0.1.0
code around. I can’t have duplicate module names in go.mod
, but I can play a sneaky trick with replace
directives:
require (
github.com/joeblubaugh/lib v0.1.0
joe/upgraded v0.0.1
)
replace joe/upgraded v0.0.1 => github.com/joeblubaugh/lib v0.5.0
By introducing a fake module name and a replace directive, I can import code from two different versions into the same program and incrementally move over to the new package:
import (
github.com/joeblubaugh/lib
joe/upgraded
)