Dependency Management in Go

Tell me something...If you are craving noodles, do you make them from scratch? Right from the flour and everything ? Of course not...you got instant noodles for that! Its the same in the world of Code. How?

Let's say you are developing an application and you sort of realise that you need a sophisticated logging mechanism in it. What do you do? Do you go around writing it from scratch? Of course not! There are plenty of people who would have already written such a thing. So why reinvent the wheel ?

(P.S. The noodle analogy may be terrible, but you get the idea :P)

Enter Packages!

A package is just any piece of code that can be used to carry out a certain specific function, irrespective of the OS. Developers are always using packages written by others and conversely, publishing their code as packages for others to use. And this is the case for every programming language.

Typically, an application will at least use a couple of different packages. And with this, comes a whole new headache of managing packages. You need to keep track of the packages used with their correct versions. Moreover, some packages may in turn use some other packages. Well.... Are you going to keep a tab on that too?

Thankfully, all programming languages have a dependancy management mechanism already in place which takes care of this. So does Go.

Go Dependancy Management

Go handles package and dependancy management using Go Modules. A Go module is a group of packages versioned together as a single unit. Modules have the exact dependency requirements so that builds can be reproduced easily for different user.

Modules are now used over  $GOPATH which was a roadblock for new Go developers who had trouble understanding why things had to go into a specific directory.

Before getting into detail, let's create a module first. Creating a module is as simple as creating a folder and running the go mod init <module-name> command inside it.

//create a project folder
mkdir my-module

//enter into the project folder
cd my-module

//create a module 
go mod init github.com/gochronicles/my-modules
Note : We created the module starting with "github.com/..." this is the import path for the module.  If someone else wants to use your module then they can directly import via the import path.  A "go get" command will fetch the module directly from the repository link in the import path and fetch the module (given that it is publicly available on GitHub).  So, creating a package in Go is as simple as uploading your code to a public GitHub Repository.

Notice something ? You'll have 2 new files in the folder: go.mod and go.sum.

The go.mod and go.sum Files

When you create a module,  the go.mod and go.sum files are automatically created. These two files are like a ledger, maintaining all the dependancies.

  • The go.mod files contains a list of dependencies along with their respective versions that are required for building the module successfully. It also has the module's "import path". In simple terms, the import path can be used in other applications to import code from the module.
  • The go.sum file stores the hash values of contents of the module versions. This ensures that the same content will be fetched whenever and wherever the module is downloaded. This is a generated file and should almost be never be edited.

The "go get" Command

The go get command is then used to add a dependancy. Usually, most publicly available modules have names that are urls or GitHub repository links so that "go get" can directly  install from these. However there isn't any compulsion on this. If your module is going to be used internally then you can have a simple name for your module.

//get dependency for logging
go get go.uber.org/zap
Install a dependancy

If you now head over the go.mod file, you will see the below content:

module github.com/gochronicles/go-modules

go 1.15

require go.uber.org/zap v1.16.0 // indirect
Note: Why is there a commented "indirect" ? This means that the dependency is not being used  anywhere in the code directly, so an "indirect" comment is automatically added.

go get can be used to download a particular version of the dependency or based on a particular commit hash. Consider zap's commit hash to be as shown below 5b4722d3797ca2e22f3c16dbe71a97b9c7783c98

go get go.uber.org/zap@v1.16.0

  OR
  
go get go.uber.org/zap@5b4722d3797ca2e22f3c16dbe71a97b9c7783c98

Now the newly installed dependancy can be easily used. Create a new main.go file containing the following code

package main

import "go.uber.org/zap"

func main() {
	logger, _ := zap.NewProduction()
	logger.Info("successfully performed")
}
main.go

And run the code

go run main.go

go mod tidy
Commands to be run 

go mod tidy, as the name suggests will clean up the  go.mod file. It removes unwanted dependencies which are not being used by the application.

Versioning for Go modules

Go modules follow semantic versioning. In Go, the dependencies will be under the following versions:

* v0 - unstable
* v1 - stable minor
* v2 - stable major

go get go.uber.org/zap -> v0/v1

for any version above 1 or 2+ it should be specified in the path as

go get go.uber.org/zap/v2 or v3 // if v2 or v3 exists

This applies to the import statements within go files as well

Tip: Whenever you want to find out the versions for a particular dependency use the command as below. Based on the versions it is 2 and above don't forget to append the path of the version while using go get or in import statements.
go list -m -versions go.uber.org/zap

go.uber.org/zap v0.1.0-beta.1 v1.0.0-rc.1 v1.0.0-rc.2 v1.0.0-rc.3 v1.0.0 v1.1.0 v1.2.0 v1.3.0 v1.4.0 v1.4.1 v1.5.0 v1.6.0 v1.7.0 v1.7.1 v1.8.0 v1.9.0 v1.9.1 v1.10.0 v1.11.0 v1.12.0 v1.13.0 v1.14.0 v1.14.1 v1.15.0 v1.16.0
Listing versions of a dependency

Extra: Caching of Dependencies - Scaling for Production Usage
Modules downloaded by go get from source control like GitHub or Gitlab are stored locally. In certain production scenarios, however, creating builds via this method can be slower and difficult due to network or security constraints. Platforms like Athens and Jfrog GoCenter help download modules faster and keep it immutable so that time to build and reproducible builds can be achieved in a faster way.

There! we now have a basic understanding about how packages are managed in Go. And to be honest, its quite simple! If you want to know more, we are listing a few reference links below. Also, do make sure to read our other blogs in the "Kickstart Go" series for brushing up on your Go fundamentals!

Using Go Modules - The Go Blog
Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.