Released as part of Go 1.11 and now enabled by default in Go 1.16, Go Modules is the recommended way to manage dependencies, in this post I will cover the steps and commands needed for working with Go modules on a day-to-day basis.
What is a Module?
According to the official documentation a
Module is (emphasis mine):
is a collection of Go packages stored in a file tree with a go.mod file at its root
This means a repository is not by definition a package but rather the folder with a
go.mod at its root; so in other words, a repository in practice could have multiple modules.
The content of the
go.mod file defines the following:
- Module name
- Go version
- Required dependencies (if any)
- Replaced dependencies (if any)
- Excluded dependencies (if any)
The Module name as well as the Go version are the only required parts of a
go.mod because in practice you can create a module that only uses the standard library.
For example a
go.mod could look like this:
module github.com/MarioCarrion/todo-api go 1.16 require ( github.com/confluentinc/confluent-kafka-go v1.7.0 github.com/deepmap/oapi-codegen v1.5.1 github.com/elastic/go-elasticsearch/v7 v7.12.0 // ... more packages )
Creating a Module
To create a module we have to use the
mod tool in the form of:
go mod init <module name>
Under the root folder that is going to represent the final module, in the example above, I used the following to create it:
go mod init github.com/MarioCarrion/todo-api
Other than init, the tool
mod also defines other useful commands such as:
- edit to edit the
go.modfile, meant to be used by CLIs and scripts,
- tidy to add missing and remove unused modules.
And a few more, you can use
go help mod to get more details about the other ones. I think the most used command after init is tidy because after adding a new module as a dependency you have to call it to clean up your
Working with Modules
The other tools in the Go toolchain are module-aware, so for example
test when detecting new dependencies added, they invoke behind the scenes the required mod tools to download the needed packages and update
Adding a new dependency, for example
github.com/gorilla/mux, can be done a few different ways:
- Add the import directly into the new code, and
go mod downloadand
go mod tidy.
go get github.com/gorilla/mux,
- Add the import into the code, and
go mod downloadand
go mod tidy.
The difference in steps is whether we want to explicitly get a concrete version or not, so assuming we want to get version
v1.8.0 we should be using
go get email@example.com in the first step of the second workflow.
This versioning mechanism brings another important thing to know: Semantic Versioning, I covered SemVer when I talked about Versioning REST APIs, the idea is exactly the same, module authors are expected to follow those guidelines when publishing their modules.
Upgrading a module follows a similar workflow by using
get, for example to upgrade the module we use:
go get -u github.com/gorilla/mux
That will update the module to the most recent version, if we are only trying to see if there are updates available then using something like this tells us:
$ go list -m -u github.com/elastic/go-elasticsearch/v7 github.com/elastic/go-elasticsearch/v7 v7.12.0 [v7.13.0]
The output above indicates there’s a new release for the Elasticsearch package.
Downgrading a module follows a similar step we did when indicating a version but this time we use a version lower to the one we currently have, for example:
go get firstname.lastname@example.org
Like I mentioned before SemVer is a fundamental part of versioning in Go, understanding and following those guidelines is a must when creating Go Modules; those versions in the end are indicated as tags in the versioning control system.
For example to publish a version
v1.2.3 in a module that happens to use Git, we should create a tag using something like:
git tag v1.2.3 git push origin --tags
In cases where we have the need to create a major version, for example from
v2.x.x; there are two strategies to follow:
- Create branches, or
- Create folders
When using Branches the idea is to literally create a new branch, for example in Git:
git checkout -b v2
A real example of this approach is the
github.com/olivere/elastic package for Elasticsearch.
When using Folders the idea is to create a folder with the major version name, for example
v2 that is going to include a new
go.mod. A real life example of this the
For both cases we must modify the
go.mod file to indicate that
v2 version in the module name, for example:
module github.com/MarioCarrion/todo-api/v2 go 1.16 // ... everything else
If you’re looking to expand more about Go Modules, I recommend reading the following links:
- The Go Blog: Using Go Modules
- Go Doc: Developing and publishing modules
- Go Doc: Developing a major version update
- Go Modules Cheat Sheet
- 18 Essential Go Module tidbits for a newbie