← Go, End to End

Go, End to End

Modules, Dependencies, and Versioning

go.mod and go.sum

go.mod declares your module's path, the Go version it targets, and its direct/indirect dependencies. go.sum records cryptographic checksums of every dependency version ever used, so builds are reproducible and a compromised package registry can't silently swap in different code under the same version number.

go
module github.com/you/yourproject

go 1.22

require (
	github.com/some/dependency v1.4.0
)

go mod init github.com/you/yourproject creates the initial go.mod. go get github.com/some/dependency adds or updates a dependency. go mod tidy reconciles go.mod/go.sum with what your code actually imports, adding anything missing and removing anything unused, worth running before every commit that touches imports.

Semantic import versioning

Go has a genuinely unusual convention here: when a module reaches a breaking v2 release, the module PATH itself changes to include /v2, not just the version number in go.mod. A module at github.com/you/yourproject becomes github.com/you/yourproject/v2 for its v2+ releases, and both can be imported into the same program simultaneously without conflict, since they're literally different import paths.

go
import (
	old "github.com/you/yourproject"
	newv "github.com/you/yourproject/v2"
)

This solves a real problem other ecosystems handle worse: two dependencies in your tree that each need a different major version of a shared transitive dependency simply import different paths and coexist, rather than forcing a single resolved version that might break one of them.

Vendoring

go mod vendor copies all dependencies into a vendor/ directory checked into your own repository, so a build doesn't need network access to a module proxy at all. Less common now that module proxies are reliable and fast, but still used in locked-down environments (air-gapped builds, strict supply-chain requirements) where fetching anything at build time isn't acceptable.

Go workspaces

go.work (introduced in Go 1.18) lets you develop against multiple local modules simultaneously without publishing intermediate versions or using replace directives in every consuming go.mod. If you're working on a library and an application that depends on it in the same local checkout, a workspace file at the top listing both module directories makes changes in the library immediately visible to the application, without a commit/tag/publish cycle in between.

go
// go.work
go 1.22

use (
	./mylib
	./myapp
)

This is a local development convenience, go.work is never something you'd expect consumers of your published module to need or see.