Module 2 · Lesson 3 · ~20 min read
A Go module is the unit of versioning, distribution, and dependency. Two files run the show: go.mod and go.sum. Get those right and most dependency questions answer themselves.
A module is a directory tree containing a go.mod file at the root. The go.mod declares:
github.com/digital-asset/canton-go-client).// go.mod
module github.com/example/cantonctl
go 1.22
require (
google.golang.org/grpc v1.60.0
google.golang.org/protobuf v1.32.0
github.com/spf13/cobra v1.8.0
)
require (
// indirect — pulled in transitively, not directly imported by this module
golang.org/x/net v0.20.0 // indirect
)
| Command | What it does |
|---|---|
go mod init <path> | Create a new module in the current directory. |
go mod tidy | Add missing imports to go.mod, remove unused ones, normalize. |
go get <module>@<version> | Add or upgrade a specific dependency. |
go get -u ./... | Upgrade all direct dependencies to latest minor/patch. |
go build ./... | Build everything in the module. |
go test ./... | Test everything. |
go mod why <module> | Explain why a particular module is in your dep graph. |
go mod graph | Print the full dep graph (machine-readable). |
go mod vendor | Copy all deps into a vendor/ directory for hermetic builds. |
For every direct and indirect dependency, go.sum records cryptographic hashes of the downloaded module content. The Go toolchain verifies these on every build. Commit go.sum. It is your supply-chain integrity guarantee.
# Excerpt from a real go.sum
google.golang.org/grpc v1.60.0 h1:6+QweTERNxz3HQECXq9CqGXxUWxSmGPyyW+vMltnpzs=
google.golang.org/grpc v1.60.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
If a downloaded module's hash doesn't match what's in go.sum, the build fails. This catches both silent registry tampering and accidental typo-squatting.
Go modules use semver: v1.2.3 = MAJOR.MINOR.PATCH. The major version above 1 is part of the import path:
import "github.com/foo/bar" // v0 or v1
import "github.com/foo/bar/v2" // v2.x.x
import "github.com/foo/bar/v3" // v3.x.x
This is "semantic import versioning." It guarantees that two major versions of a module can coexist in your dep graph without conflict — useful when a transitive dep upgrades but you can't.
v0.x.y versions are explicitly "no compatibility guarantees." A surprising number of widely-used Go libraries live perpetually in v0 — the Go community is comfortable with that. The Canton ecosystem will likely include some.
// In go.mod, while developing locally:
replace github.com/example/canton-protos => ../canton-protos
Forces the named dependency to come from a local path instead of the registry. Useful when you're co-developing two modules. Remove before committing — replace directives are toxic in production.
Run go mod vendor and you get a vendor/ directory containing every dependency's source. Subsequent builds use the vendored copy instead of the module cache.
When to vendor:
When not: small projects, libraries (vendor in apps, not libs), public open-source where the cache works fine.
By default, Go fetches modules through proxy.golang.org — a Google-run cache that:
sum.golang.org) which Go cross-checks for tampering.For private modules: set GOPRIVATE=github.com/your-org/* to skip the proxy and sumdb for those paths. Required when working with a closed-source Canton-related repo.
If you have several modules in one repo, go.work at the top level lets the toolchain treat them as a unit during local development.
// go.work
go 1.22
use (
./client
./server
./shared
)
Inside a workspace, dependencies between local modules are resolved locally — no replace directives, no path gymnastics. Workspaces are a development-time tool; they don't ship to production.
go mod tidyYou add an import, build works locally because the cache has it, but CI fails because go.mod wasn't updated. Run tidy before every commit. Many teams have a pre-commit hook for it.
go.mod and GOPATHPre-modules Go used $GOPATH/src. Modern Go uses $GOMODCACHE (default ~/go/pkg/mod). If a tutorial says "put your code under $GOPATH/src/github.com/...", it's pre-2018 and out of date. Ignore.
If you import github.com/foo/bar and the maintainer cuts v2, your code does not automatically upgrade. v2 is at a different import path. go list -m -u all shows what's available; go get github.com/foo/bar/v2 moves you forward.
go.mod declares the module identity, version, and direct dependencies. go.sum locks the exact content. Commit both.go mod tidy is your friend — run it constantly.github.com/foo/bar/v2.replace directives for local dev. Remove before committing.go.work) for multi-module local dev. Don't ship them.