Special Delivery Go Modules Dependency Package Management Overview

Special Delivery Go Modules Dependency Package Management Overview #

Hello, I’m Kong Lingfei. Today, we have a special broadcast as an extra episode.

In Go project development, package management is a crucial aspect. Poor handling of dependencies can lead to compilation failures. Moreover, Go’s package management has a certain level of complexity, so it is necessary for us to learn the official recommended package management solution in Go: Go Modules.

In this lecture, I will first introduce the history of Go’s package management tools, and then provide a detailed explanation of the current official solution, Go Modules. Go Modules mainly consists of the go mod command line tool, module downloading mechanism, and two core files: go.mod and go.sum. Additionally, Go Modules also provides some environment variables to control its behavior. In this lecture, I will introduce these components separately.

Before diving into these topics, let’s start with a basic understanding of Go Modules.

Introduction to Go Modules #

Go Modules is an official Go package management solution introduced by the Go team. It is based on vgo and has the following features:

  • Simplifies package management.
  • Supports versioning.
  • Allows multiple versions of the same module to coexist.
  • Verifies the hash value of dependent packages to ensure package consistency and enhance security.
  • Built into almost all Go commands, including go get, go build, go install, go run, go test, go list, etc.
  • Has Global Caching feature, which means that the same module versions from different projects are cached only once on the server.

Starting from Go 1.14 and later versions, the Go team recommends the use of Go Modules in production environments. As a result, future Go package management solutions will gradually converge on Go Modules. There are many concepts related to Go Modules, which I will summarize as “6-2-2-1-1”. Each concept will be discussed in detail later in this article.

  • Six environment variables: GO111MODULE, GOPROXY, GONOPROXY, GOSUMDB, GONOSUMDB, GOPRIVATE.
  • Two concepts: Go module proxy and Go checksum database.
  • Two main files: go.mod and go.sum.
  • One primary management command: go mod.
  • One build flag.

History of Go Package Management #

Before delving into Go Modules, let’s take a look at the history of Go package management. Since the introduction of Go, there have been many different package management solutions without a unified official solution, leading to confusion and inadequate resolution of some issues in Go package management. The history of Go package management is shown in the following illustration:

This illustration depicts the various stages of development in Go dependency management tools. Next, I will focus on explaining five of these stages in chronological order.

Before Go 1.5: GOPATH #

Before Go 1.5, there was no version control, and all dependencies were placed in the GOPATH. This method did not support the management of multiple versions of packages, and packages could only be located within the GOPATH directory. If both Project A and Project B relied on different versions of the same Go package, each project had to have its own GOPATH, with the corresponding version of the package placed in its respective GOPATH directory. Switching between project directories also required switching the GOPATH, which added complexity to development and implementation.

Go 1.5: Vendoring #

Go 1.5 introduced the vendor mechanism, which became enabled by default in Go 1.6. In this mechanism, each project’s root directory could have a vendor directory that stored its Go dependencies. When compiling Go source code, Go first searched for dependencies in the vendor directory of the project’s root directory. If not found, it looked for them in the vendor directory under the GOPATH. If still not found, it searched the GOPATH.

This approach addressed the issue of multiple GOPATHs, but as the number of project dependencies increased, the vendor directory became larger, resulting in larger repositories overall. In the vendor mechanism, it was not uncommon for the vendor directory of a medium-sized project to be hundreds of megabytes in size.

“Variety of Dependent Package Management Tools”: Emergence of Multiple Go Dependency Package Management Tools #

During this stage, the community developed many Go dependency package management tools. Here, I will introduce three well-known tools:

  • Godep: A tool for managing package dependencies. Godep has been used in Go projects such as Docker, Kubernetes, and CoreOS.
  • Govendor: This tool had more features than Godep and used a vendor.json file in the vendor directory to record the versions of dependencies.
  • Glide: A relatively comprehensive package management tool that used glide.yaml to record dependency information and glide.lock to track specific modifications to each package.

Govendor and Glide were developed after Go introduced support for vendor mode, while Godep could be used even before vendor mode was supported. After Go supported vendor mode, Godep also adopted the vendor mode.

Go 1.9: Dep #

For new users building projects from scratch, Glide was a good choice, as it met their needs. However, the chaotic situation in Go dependency management tools was eventually resolved by the official Go team. The Go team accepted Dep, developed collaboratively by the community, as an official experiment. For a considerable period, Dep became the de facto official package management tool, serving as the standard.

Since Dep is now in the past as an official experiment, we do not need to delve deeper into it. Let us now move on and explore who is the future official experiment.

After Go 1.11: Go Modules #

Go 1.11 introduced the Go Modules mechanism, which evolved from vgo and became the official package management tool for Go. In Go 1.13, Go Modules was set as the default Go management tool, and in Go 1.14, the Go team officially recommended the use of Go Modules in production environments and encouraged all users to migrate from other dependency management tools. Finally, Go now has a stable and official package management tool.

In this section, I have covered the history of Go dependency management tools. Next, I will introduce how to use Go Modules.

Packages and Modules #

Go programs are organized into Go packages, which are collections of Go source files compiled together in the same directory. Functions, types, variables, and constants defined in one source file are visible to all other source files in the same package.

Modules are collections of Go packages stored in a file tree, and the root directory of the file tree contains a go.mod file. The go.mod file defines the name of the module and its dependencies, with each dependency specifying an import path and a semantic versioning. Import paths and semantic versions accurately describe a dependency.

It’s important to note that "module" != "package". The relationship between modules and packages is more like that between sets and elements. A package belongs to a module, and a module is a collection of zero or more packages. The following code snippet references some packages:

import (
    // Go standard package
    "fmt"

    // Third-party package
    "github.com/spf13/pflag"

    // Anonymous package
    _ "github.com/jinzhu/gorm/dialects/mysql"

    // Internal package
    "github.com/marmotedu/iam/internal/apiserver"
)

Here, fmt, github.com/spf13/pflag, and github.com/marmotedu/iam/internal/apiserver are all Go packages. There are four types of packages in Go, which I will explain separately.

  • Go standard package: Packages that are shipped with Go in the Go source code directory.
  • Third-party package: Packages provided by third-party sources, such as those from github.com.
  • Anonymous package: Packages that are imported but not used. Typically, we only want to use the side effects produced by importing the package, such as referencing package-level variables, constants, structures, interfaces, as well as executing the package’s init() function.
  • Internal package: Packages internal to a project, located in the project’s directory.

The following directory defines a module:

$ ls hello/
go.mod  go.sum  hello.go  hello_test.go  world

The hello directory contains a go.mod file, indicating that it is a module that includes the hello package and a subpackage, world. The directory also contains a go.sum file, which is used by the Go tool to verify the legitimacy of dependencies during the build process. For now, just have a basic understanding, as I will explain the go.sum file in detail later.

Go Modules Commands #

The management command for Go Modules is go mod, which has many subcommands. You can use go help mod to get a list of all the commands. Let me explain each command in detail.

  • download: Downloads all the dependencies listed in the go.mod file.
  • edit: Edits the go.mod file.
  • graph: Displays the existing dependency graph.
  • init: Initializes the current directory as a new module.
  • tidy: Adds missing modules and removes unused modules. By default, Go does not remove unused dependencies listed in the go.mod file. You can use the go mod tidy command to clean them up when they are no longer needed.
  • vendor: Saves all the dependencies to the vendor directory in the current directory.
  • verify: Checks if the dependencies of the current module are stored in the locally downloaded source code cache, and verifies if there are any modifications after downloading.
  • why: Displays why a certain module is a dependency.

Go Modules Toggle #

If you want to use Go Modules, you still need to ensure that the Go Modules feature is enabled in Go 1.14. You can use the environment variable GO111MODULE to enable or disable it. GO111MODULE has three values, and I will explain each one:

  • auto: This is the default value in Go 1.14. Go Modules is disabled when there is no go.mod file in $GOPATH/src, and enabled in all other cases.
  • on: Enable Go Modules. It is recommended to enable it in Go 1.14 and will become the default value in future versions.
  • off: Disable Go Modules. It is not recommended.

So, to enable Go Modules, you can set the environment variable export GO111MODULE=on or export GO111MODULE=auto. It is recommended to directly set export GO111MODULE=on.

Go Modules use semantic versioning. When tagging the release version of the module we are developing, we need to follow the requirements of semantic versioning. Versions that do not comply with the semantic versioning specification cannot be retrieved.

Module Download #

When executing commands such as go get, modules will be automatically downloaded. Next, I will introduce how the go command downloads modules. There are three main download methods:

  • Downloading through a proxy;
  • Downloading by specifying a version number;
  • Downloading the minimum version.

Downloading through a proxy #

By default, the Go command downloads modules directly from Version Control Systems (VCS) such as GitHub, Bitbucket, Bazaar, Mercurial, or SVN.

In Go 1.13, a new environment variable GOPROXY was introduced to set up Go module proxies. The module proxy allows the Go command to download modules directly from the proxy server. The default value of GOPROXY is https://proxy.golang.org,direct, and multiple proxy servers can be specified using commas, such as GOPROXY=https://proxy.golang.org,https://goproxy.cn,direct. When downloading modules, the specified proxy server is given priority. If the download fails, such as when the proxy server is inaccessible or the HTTP response code is 404 or 410, the Go command will attempt to download from the next proxy server.

direct is a special indicator that instructs Go to fetch the module from the module’s source address (such as GitHub) when the previous Go module proxy in the value list returns a 404 or 410. Go will automatically try the next one in the list when encountering direct, and will terminate when encountering EOF, throwing an error similar to invalid version: unknown revision.... If GOPROXY=off, the Go command will not attempt to download modules from the proxy server.

The introduction of Go module proxies brings many benefits, such as:

  • Chinese developers who cannot access domains such as golang.org, gopkg.in, go.uber.org can set GOPROXY to a proxy server accessible in China, solving the issue of failed dependency downloads.
  • The Go module proxy permanently caches and stores all dependencies, and once cached, these dependencies cannot be changed. This means that we no longer need to maintain a vendor directory and can avoid the storage space occupied by maintaining the vendor directory.
  • Because the dependencies exist permanently in the proxy server, even if the module is deleted from the Internet, it can still be obtained through the proxy server.
  • Once a Go module is stored in the Go proxy server, it cannot be overwritten or deleted, which protects developers from potential attacks due to the injection of malicious code of the same version.
  • We no longer need VCS tools to download dependencies because all dependencies are downloaded from the proxy server via HTTP.
  • Since the Go proxy independently provides the source code (.zip archive) and go.mod through HTTP, the download and build speed of Go modules is faster. Because go.mod can be obtained independently (instead of having to obtain the entire repository as before), dependency resolution is also faster.
  • Of course, developers can also set up their own Go module proxies, which gives them more control over the dependency packages and prevents download failures due to VCS downtime.

In actual development, many of our modules may need to be pulled from a private repository, and accessing them through a proxy server will result in an error. In this case, we need to add these modules to the environment variable GONOPROXY, and the hash values of these private modules will not exist in the checksum database, so they need to be added to GONOSUMDB. In general, I recommend directly setting the GOPRIVATE environment variable, whose value will be used as the default values of GONOPROXY and GONOSUMDB.

GONOPROXY, GONOSUMDB, and GOPRIVATE all support wildcard characters, and multiple domain names can be separated by commas, such as *.example.com,github.com.

For Chinese Go developers, there are currently 3 commonly used GOPROXY options to choose from, which are official, Qiniu, and Aliyun.

The official GOPROXY may not be accessible to users in China, so I recommend using Qiniu’s goproxy.cn. goproxy.cn is a non-profit project launched by Qiniu Cloud. Its goal is to provide a free, reliable, continuously online, and CDN-accelerated module proxy for Go developers in China and other parts of the world.

Downloading by specifying a version number #

Usually, we download modules using go get, and the download command format is go get <package[@version]>, as shown in the following table:

You can use go get -u to update the package to the latest version, or you can use go get -u=patch to only update the minor version, such as from v1.2.4 to v1.2.5.

Downloading the minimum version #

A module often depends on many other modules, and different modules may also depend on different versions of the same module, as shown in the following diagram:

In the above dependencies, module A depends on modules B and C, module B depends on module D, module C depends on modules D and F, and module D depends on module E. Additionally, different versions of the same module also depend on different versions of the corresponding module.

So how does Go Modules choose the versions? Go Modules compiles the dependency version list of each module and finally obtains a build list, as shown in the following diagram:

In the above diagram, the difference between the rough list and the final list lies in the duplicate reference of module D (v1.3, v1.4), and the final list chooses the v1.4 version of D.

There are two main reasons for doing this. The first is semantic version control. Both version changes, v1.3 and v1.4, of module D are considered as minor version changes. Under the constraint of semantic versioning, v1.4 must be backward compatible with v1.3, so we choose the higher version, v1.4.

The second reason is the specification of module import paths. If there is an incompatibility, the major version number changes, such as changing from v1 to v2, and the import path of the module also changes, so it will not affect the v1 version.

Introduction to go.mod and go.sum #

In Go Modules, go.mod and go.sum are two very important files. Let’s go into detail about these two files.

Introduction to go.mod file #

The go.mod file is the core file of Go Modules. Here is an example of a go.mod file:

module github.com/marmotedu/iam

go 1.14

require (
    github.com/AlekSi/pointer v1.1.0
    github.com/appleboy/gin-jwt/v2 v2.6.3
    github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
    github.com/gin-gonic/gin v1.6.3
    github.com/golangci/golangci-lint v1.30.0 // indirect
    github.com/google/uuid v1.0.0
    github.com/blang/semver v3.5.0+incompatible
    golang.org/x/text v0.3.2
)

replace (
    github.com/gin-gonic/gin => /home/colin/gin
    golang.org/x/text v0.3.2 => github.com/golang/text v0.3.2
)

exclude (
    github.com/google/uuid v1.1.0
)

Next, I will introduce go.mod from three aspects: go.mod statements, go.mod versioning, and how to modify the go.mod file.

  1. go.mod statements

The go.mod file contains four statements: module, require, replace, and exclude. Let me explain their functions:

  • module: Used to define the module path of the current project.
  • go: Used to set the expected Go version, currently only serves as an identifier.
  • require: Used to set a specific module version, with the format <import path> <version> [// indirect].
  • exclude: Used to exclude a specific module version from usage. If we know that a certain version of a module has severe issues, we can exclude that version.
  • replace: Used to replace one module version with another. The format is $module => $newmodule, where $newmodule can be a relative path on the local disk, such as github.com/gin-gonic/gin => ./gin. It can also be an absolute path on the local disk, such as github.com/gin-gonic/gin => /home/lk/gin. It can even be a network path, such as golang.org/x/text v0.3.2 => github.com/golang/text v0.3.2.

It is important to note that although we replace $module with $newmodule, the import path in the code remains $module. Replace is frequently used in practical development. The following scenarios may require the use of replace:

  • After enabling Go Modules, the cached dependency packages are read-only. However, during daily development and debugging, we may need to modify the code of the dependency package for debugging purposes. In this case, we can save the dependency package to a new location and replace it in go.mod.
  • If some dependency packages cannot be downloaded during the execution of the Go command, we can download the dependency package through other means, upload it to the development build server, and replace it in go.mod.
  • In the early stages of project development, project A depends on a package from project B, but for various reasons, project B has not been pushed to the repository. In this case, we can replace the dependency package with the local disk path of project B in go.mod.
  • Accessing various packages in golang.org/x from China requires going through a firewall. In go.mod, we can use replace to replace it with the corresponding library on GitHub, for example, golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0.

Note that exclude and replace only apply to the current main module and do not affect other modules that the main module depends on.

  1. go.mod versioning

The go.mod file contains various version formats, and I know many developers are confused about them in their everyday use. Here, I will explain them in detail:

  • If a module has a tag that conforms to the semantic version format, the tag value will be displayed directly, for example, github.com/AlekSi/pointer v1.1.0.
  • Except for v0 and v1, the major version number must appear at the end of the module path explicitly, for example, github.com/appleboy/gin-jwt/v2 v2.6.3.
  • For modules without tags, the Go command will select the latest commit on the master branch and generate a version number that conforms to the semantic version based on the commit time and hash value. For example, github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535.
  • If the module name does not match the version in go.mod, for example, the module name is github.com/blang/semver, but the version is v3.5.0 (it should normally be github.com/blang/semver/v3), Go will append +incompatible after the version number in go.mod.
  • If the package in go.mod is an indirect dependency, // indirect will be added as a comment, for example, github.com/golangci/golangci-lint v1.30.0 // indirect.

Here, it is important to note that Go Modules requires the version format of modules to be v<major>.<minor>.<patch>. If the <major> version number is greater than 1, the version number should also be reflected in the module name. For example, if the module github.com/blang/semver increases to v3.x.x, the module name should be github.com/blang/semver/v3.

Let’s delve into the cases where // indirect appears. In principle, what appears in go.mod are direct dependencies. However, if either of the following two situations occurs, indirect dependencies will be added to go.mod:

  • Direct dependencies do not enable Go Modules: If module A depends on module B, and module B depends on B1 and B2, but B does not have a go.mod file, B1 and B2 will be recorded in the go.mod file of A and appended with // indirect.
  • Direct dependencies have missing dependencies in go.mod: If module A depends on module B, and module B depends on B1 and B2, and B has a go.mod file, but only B1 is recorded in B’s go.mod file, B2 will be recorded in the go.mod file of A and appended with // indirect.
  1. Methods to modify the go.mod file

To modify the go.mod file, we can use the following three methods:

  • The Go command automatically modifies the files during runtime.
  • Manually edit the go.mod file, and then execute go mod edit -fmt to format the go.mod file.
  • Use the go mod subcommands to make modifications.

In practical use, I recommend using the third method, as it is less likely to cause errors compared to the other two methods. Here is how you can use it:

go mod edit -fmt  # Format go.mod
go mod edit -require=golang.org/x/text@v0.3.0  # Add a dependency
go mod edit -droprequire=golang.org/x/text  # Remove a dependency
go mod edit -replace=github.com/gin-gonic/gin=/home/colin/gin  # Replace a module version
go mod edit -dropreplace=github.com/gin-gonic/gin  # Remove a replace directive
go mod edit -exclude=golang.org/x/text@v0.3.0  # Exclude a specific module version
go mod edit -dropexclude=golang.org/x/text@v0.3.0  # Remove an exclude directive

Introduction to go.sum file #

Go downloads package source code based on the dependencies and their versions recorded in the go.mod file. However, the downloaded packages may be tampered with, and the locally cached packages may also be tampered with. Simply having a go.mod file does not guarantee package integrity. To address this potential security issue, Go Modules introduces the go.sum file.

The go.sum file is used to record the hash values of each dependency package. During the build process, if the locally cached package’s hash value does not match the one recorded in the go.sum file, the build will be rejected. The go.sum file records all the direct and indirect dependencies.

It’s worth noting that to prevent cached modules from being altered, the packages under $GOPATH/pkg/mod are read-only and cannot be modified.

Next, I will explain go.sum from three aspects: the content of the go.sum file, generating a go.sum file, and verifying the go.sum file.

  1. Content of go.sum file

Here is an example of the content of a go.sum file:

golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

In the go.sum file, each line consists of the module name, version, hash algorithm, and hash value, formatted as <module> <version>[/go.mod] <algorithm>:<hash>. Currently, from Go 1.11 to Go 1.14, there is only one algorithm, SHA-256, represented as h1.

Normally, each dependency package includes two records: one for the hash value of all files in the dependency package, and the other for the hash value of the go.mod file of that dependency package. For example:

rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=

However, if a dependency package does not have a go.mod file, only the hash value of all files in the dependency package is recorded, which means there is only the first record. The additional record for the go.mod hash value is mainly for calculating the dependency tree without having to download the complete dependency package version. Only the go.mod file is needed for the calculation.

  1. Generating the go.sum file

When Go Modules is enabled, if our project needs to import a new package, we usually execute the go get command, for example:

$ go get rsc.io/quote

When executing the go get rsc.io/quote command, the go get command first downloads the dependency package to $GOPATH/pkg/mod/cache/download. The downloaded dependency package is named in the format $version.zip, for example, v1.5.2.zip.

After the download is complete, go get hashes the zip package and stores the result in the $version.ziphash file, for example, v1.5.2.ziphash. If the go get command is executed in the project’s root directory, go get will update both the go.mod and go.sum files. For example, go.mod adds a new line require rsc.io/quote v1.5.2, and go.sum adds two lines:

rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
  1. Verification

During the build process, the go command retrieves all the dependency packages from the local cache and calculates the hash values of these packages. It then compares these hash values with the ones recorded in the go.sum file. If the hash values do not match, the verification fails, and the build process is stopped.

Verification failure may be caused by the locally specified version of a dependency package being modified, or the hash values recorded in go.sum being incorrect. However, the go command tends to believe that the dependency package has been modified. When we go get a dependency package, the package’s hash value is verified against the checksum database, and it is only added to the go.sum file if the verification passes. In other words, the hash values recorded in the go.sum file are trustworthy.

The checksum database can be specified using the GOSUMDB environment variable, with the default value being a web server, sum.golang.org. This service can be used to query the hash values of specific versions of dependency packages, ensuring that the retrieved module version data has not been tampered with.

If GOSUMDB is set to off, or if the -insecure flag is used with go get, the go command will not perform secure checksum verification on downloaded dependency packages. This poses a security risk, so I recommend enabling the checksum database. If you have very high security requirements and cannot access sum.golang.org, you can set up your own checksum database.

It is worth noting that the Go checksum database can be proxied by Go module proxies. Therefore, when we set GOPROXY, we usually do not need to set GOSUMDB separately. Also, remember to commit the go.sum file to your Git repository.

Module Download Process #

Earlier, I introduced the overall process of module downloads and also mentioned two files, go.mod and go.sum. Because there’s quite a lot of content, I’ll summarize it with an image:

Finally, I would like to introduce the global cache of Go modules. In Go modules, only one copy of the same module version is cached, which is shared by all other modules. Currently, all module version data is cached in $GOPATH/pkg/mod and $GOPATH/pkg/sum. In the future, it may move to $GOCACHE/mod and $GOCACHE/sum, and I believe this could happen after GOPATH is deprecated. You can use go clean -modcache to clear all caches.

Summary #

Go dependency management is a key feature in the Go language. Prior to Go 1.11, there was no official dependency management tool, and although there were several existing solutions in the industry, their effectiveness was not ideal. It was not until Go 1.11 that Go introduced the official dependency management tool, Go Modules. This is the dependency management tool I recommend you choose when developing Go projects.

Go Modules provides the go mod command to manage Go dependencies. go mod has many subcommands, each of which serves a different purpose. For example, initializing the current directory as a new module, adding missing modules, removing unused modules, and so on.

In Go Modules, there are two very important files: go.mod and go.sum. The go.mod file is the core file of Go Modules, and Go will download package source code based on the dependencies and their versions recorded in go.mod. The go.sum file is used to record the hash value of each dependency package. During the build process, if the local hash value of a dependency package does not match the one recorded in go.sum, the build will be rejected.

When downloading dependency packages, Go can use a proxy or specify a version number. If no version number is specified, Go Modules will use custom rules to select the minimum version for download.

Homework Exercises #

  1. Think about what risks there are if you don’t submit go.sum.
  2. Find a Go project that hasn’t used Go Modules to manage its dependencies and switch its dependency management to Go Modules.

Feel free to leave a comment in the discussion area and let’s see you in the next lesson.