Package Management with Go Modules

Golang Mar 29, 2021

Package management is an essential key to every project setup. At Halodoc, we rely heavily on good package management to improve delivery time for developers. Having a standard package manager across projects allows our developers to easily set up any project once they are accustomed to the tech stack.

A package manager itself is used as a tool to create the environment by downloading, updating, or removing project dependencies. By determining the specific version of the package, it can download or update the package, typically from a remote host. Go modules is one of many package managers to deal with dependencies in Go.

What is Go Module

A Module is a collection of Go packages stored in a file tree under $GOPATH/pkg folder with a go.mod file at its root. This file defines the module’s path, which is also the import path used for your project, and its dependency requirements, which are the other modules needed for a successful build.

As of Go 1.11, the go command (go build, go run, and go test) automatically checks and adds dependencies required for imports as long as the current directory or any parent directory has a go.mod. Projects are also able to be located outside of $GOPATH/src folder.

Go Modules introduce 2 new files into a Go project:

  • go.mod specifies a module name, dependencies, and the minimal versions.
  • go.sum is an auto-generated dependencies lock file.

Both files are located at the root level of the project.

Initially, at Halodoc we have been using Go Dep as our package manager for Go. Ever since the deprecation of Dep in 2020, we have decided to move to Go modules since it became the official recommendation for dependencies and applied in the Go ecosystem.

Most of the Go Dep feature is available on Go Modules:

  • Similar to Dep, the Go module is also able to put the dependencies to a /vendor directory so that different projects can depend on different versions of the same packages.
  • Go Modules is a built-in feature, don’t need to install additional tools like go dep.
  • Allow and recommend projects to be located outside of $GOPATH.
  • Automatically check for dependencies when build, run, or test

Go Module & Dep command comparison:

Command Go Modules Go Dep
Initiate project go mod init dep init
Add dependecny go get <libsName> dep ensure -add <libsName>
Install in vendor go mod vendor dep ensure
Remove unused modules go mod tidy -

Getting Started

  1. Initialize module

First, we need to initialize the module inside your project:

$ cd gitlab.com/go-module-project #project directory
$ go mod init

A go.mod file will be created inside with the name of your project directory as the module and the version of Go you are using.

initial go.mod contents

2. Installing dependencies

After the go.mod file is initialized, you may download specific versions of modules using the go get command. Example:

$ go get -u github.com/gin-gonic/gin

The package manager will check and download the latest tag of the dependency. With the -u argument all child dependencies required by it will be downloaded as well. Once a dependency has been downloaded for the project, the go.sum file will be added to your project. Contents inside the go.mod file will also be updated to list all the versions of dependencies that have been downloaded.

go.mod contents after go get
snippet of go.sum file

When writing the code inside your Go project, there will be cases in which the dependencies have not been downloaded by go get command but are listed as an import statement. To validate this, you may go ahead with running/building your project which will automatically check and download the compatible versions of the dependencies based on your code.

$ go build

Alternatively, you may only download the missing dependencies without having to run/build your project:

$ go mod download

By default go build/go run command also executes the go mod download command.

3. Removing unused dependencies

To remove a dependency the go mod tidy command will check into the go.mod file to see if there is any unused dependency. To illustrate this, remove the Go file which uses the github.com/gin-gonic/gin module as mentioned above during go get.

$ go mod tidy -v

The command will have the below output and unused dependencies in the go.mod and go.sum file will be removed.

$ go mod tidy -v
unused github.com/gin-gonic/gin
unused github.com/go-playground/validator/v10
unused github.com/golang/protobuf
unused github.com/json-iterator/go
unused github.com/leodido/go-urn
unused github.com/modern-go/concurrent
unused github.com/modern-go/reflect2
unused github.com/ugorji/go
unused golang.org/x/crypto
unused golang.org/x/sys
unused google.golang.org/protobuf
unused gopkg.in/yaml.v2

Updating Specific Dependency Version

During development, you may sometimes need to update the version of the module you are using to a specific branch or tag version. This can be done through command with the following examples:

Description Command
Install latest tag version go get github.com/gin-gonic/gin
Install specific tag version go get github.com/gin-gonic/gin@v1.6.2
Install specific branch go get github.com/gin-gonic/gin@master
Install specific commit go get github.com/gin-gonic/gin@1bdf86b

Alternatively, you can edit the go.mod file and run go mod download after. However, Go module has its version semantics as can see from the below case:

dependency version with branch develop

In the above example, we are trying to update the version of the module to a specific branch called "develop". By default go module will only follow the format "vMAJOR.MINOR.PATCH" (example: v2.1.5). If your version name (branch name or tag version) does not follow this, the version will be updated when you run go mod download with the following semantics "v.0.0.0-{YYYYMMDDHHmm}-{commit_id}" as shown below:

updated version name after download

Common Issues

  1. 410 Gone

You might get the following error when trying to download a dependency in your terminal:

$ go get -v gitlab.com/halodoc/example-lib
go: finding gitlab.com/halodoc/example-lib latest
go: downloading gitlab.com/halodoc/example-lib v0.0.0-20200906012644-bacd9c7ef1dd
verifying gitlab.com/halodoc/example-lib@v0.0.0-20200906012644-bacd9c7ef1dd: gitlab.com/halodoc/example-lib@v0.0.0-20200906012644-bacd9c7ef1dd: reading https://sum.golang.org/lookupgitlab.com/halodoc/example-lib@v0.0.0-20200906012644-bacd9c7ef1dd: 410 Gone

The issue above is introduced from Go 1.13. Checking dependencies will no longer be available to module paths that are not publicly available. For a private module, we will need to add GOPRIVATE environment variable locally before checking the dependency.

Single private repository:

$ export GOPRIVATE=gitlab.com/halodoc

For multiple private URLs you may append it with a comma as delimiter:

$ export GOPRIVATE="gitlab.com/halodoc,bitbucket.org/halodoc"

Conclusion

In this article, we have explained how Go Modules implementation is done for Golang package management. As standard package management, Go Modules is being used across all Golang services in Halodoc to ease project setup.


Join Us

We are always looking out for top engineering talent across all roles for our tech team. If challenging problems that drive big impact enthrall you, do reach out to us at careers.india@halodoc.com


About Halodoc

Halodoc is the number 1 all-around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, and many more. We recently closed our Series B round and In total have raised USD$100million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalized for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.