rss twitter gitlab github linkedin linkedin instagram
Learning Go: Managing dependencies using Modules
Jun 07, 2021

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.mod file, 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 go.mod.

Working with Modules

The other tools in the Go toolchain are module-aware, so for example get, build or test when detecting new dependencies added, they invoke behind the scenes the required mod tools to download the needed packages and update go.mod.

Adding a new dependency, for example github.com/gorilla/mux, can be done a few different ways:

  1. Add the import directly into the new code, and
  2. Run go mod download and go mod tidy.

Or

  1. Run go get github.com/gorilla/mux,
  2. Add the import into the code, and
  3. Run go mod download and 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 github.com/gorilla/mux@v1.8.0 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 github.com/gorilla/mux@v1.7.4

Publishing Modules

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 v1.x.x. to 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 googleapis/gax-go/v2 package.

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:


Back to posts