Modules and Packages¶
★★★★★ Intermediate
Go modules are the unit of dependency management, and packages are the unit of code organization. A module is a collection of packages with a go.mod file at the root. Packages group related code within a directory - one package per directory, one directory per package.
Key Facts¶
- A module is defined by
go.modin the root directory, containing the module path and Go version - Module path is typically the repository URL:
module github.com/user/project - Each directory is one package, package name matches directory name (except
main) - Exported identifiers start with uppercase:
MyFunc,MyType(unexported:myFunc) go getdownloads dependencies and updatesgo.modgo.sumcontains cryptographic hashes for dependency integrity - commit to VCSgo mod tidyremoves unused dependencies and adds missing ones- Go workspace mode (
go.work) lets you develop multiple modules simultaneously
Module Initialization¶
# Create a new module
mkdir myproject && cd myproject
go mod init github.com/user/myproject
# go.mod contents:
# module github.com/user/myproject
# go 1.22
go.mod Structure¶
module github.com/user/myproject
go 1.22
require (
github.com/shopspring/decimal v1.3.1
golang.org/x/sync v0.6.0
)
require (
// indirect dependencies (used by your dependencies)
golang.org/x/text v0.14.0 // indirect
)
Package Organization¶
Directory Structure¶
myproject/
go.mod
main.go # package main - entry point
internal/ # private packages, cannot be imported from outside module
config/
config.go # package config
database/
database.go # package database
pkg/ # optional, public reusable packages
auth/
auth.go # package auth
handler/
user.go # package handler
order.go # package handler (same directory = same package)
Package Naming¶
// Package name = directory name, used as import qualifier
import "github.com/user/myproject/handler"
// Usage: handler.CreateUser, handler.GetOrder
// Good: package name is a noun, functions are verbs
Naming conventions:
- Package names: short, lowercase, no underscores (names, extract, auth)
- Don't repeat package name in exports: names.Extract, NOT names.ExtractNames
- Don't use generic names: util, common, helpers (what does util.Process do?)
- One exception: when identifier IS the package: sort.Sort, context.Context
Internal Packages¶
Packages under internal/ are only importable by code in the parent directory:
myproject/
internal/
secret/ # only importable by myproject/* code
secret.go
cmd/
server/
internal/ # only importable by cmd/server/* code
config.go
Dependency Management¶
Adding Dependencies¶
# Add a specific dependency
go get github.com/shopspring/[email protected]
# Add latest version
go get github.com/shopspring/decimal@latest
# Update all dependencies
go get -u ./...
# Remove unused, add missing
go mod tidy
Import and Use¶
package main
import (
"fmt" // standard library
"github.com/shopspring/decimal" // third-party
"github.com/user/myproject/internal/db" // internal package
)
func main() {
price := decimal.NewFromFloat(19.99)
fmt.Println(price)
}
Import Grouping Convention¶
import (
// Standard library
"context"
"fmt"
"net/http"
// Third-party
"github.com/gorilla/mux"
"go.uber.org/zap"
// Internal/project packages
"github.com/user/myproject/handler"
"github.com/user/myproject/model"
)
Handling Name Collisions¶
import (
crand "crypto/rand" // renamed to avoid collision
"math/rand"
)
func main() {
// Use crand for crypto, rand for math
seed, _ := crand.Int(crand.Reader, big.NewInt(1000))
r := rand.New(rand.NewSource(seed.Int64()))
}
Versioning¶
Semantic Versioning¶
Go modules use semver: vMAJOR.MINOR.PATCH
v1.2.3
- MAJOR: breaking changes (different import path for v2+)
- MINOR: new features, backward compatible
- PATCH: bug fixes
Pre-v1 (v0.x.y): no stability guarantees
v1+: backward compatibility expected within major version
Major Version Upgrades¶
// v1 import
import "github.com/user/module"
// v2+ requires version suffix in import path
import "github.com/user/module/v2"
// Both can coexist in the same project
import (
v1 "github.com/user/module"
"github.com/user/module/v2"
)
Replace and Exclude¶
// go.mod: use local copy during development
replace github.com/user/other => ../other
// go.mod: use a fork
replace github.com/original/pkg => github.com/myfork/pkg v1.2.3
// go.mod: exclude a broken version
exclude github.com/broken/pkg v1.0.1
Workspace Mode (Go 1.18+)¶
Develop multiple modules simultaneously without replace directives.
# Create a workspace
go work init ./module-a ./module-b
# go.work file:
# go 1.22
# use (
# ./module-a
# ./module-b
# )
Workspace benefits:
- Edit module-a and module-b, changes visible to each other immediately
- No need for replace directives in go.mod
- go.work should NOT be committed to VCS (local development only)
- go.work.sum tracks hashes (also local only)
Build and Install¶
# Build current module
go build ./...
# Install a binary (goes to $GOPATH/bin or $GOBIN)
go install ./cmd/server
# Install a third-party tool
go install github.com/rakyll/hey@latest
# Cross-compile
GOOS=linux GOARCH=amd64 go build -o server-linux ./cmd/server
GOOS=windows GOARCH=amd64 go build -o server.exe ./cmd/server
Gotchas¶
- Circular imports are compile errors - if package A imports B and B imports A, Go refuses to compile. Restructure by extracting shared types into a third package, or use interfaces to break the dependency
- go.sum must be committed - it's not a lockfile (go.mod is). It contains expected hashes for dependency verification. If you .gitignore it, builds become non-reproducible and vulnerable to supply chain attacks
- Module path must match repository - if your
go.modsaysmodule github.com/user/projectbut the code lives atgitlab.com/user/project,go getwill fail. The module path IS the download path replacedirectives don't propagate - if your module usesreplaceto point to a local path, consumers of your module won't see that replace. They'll try to download the original. Usereplaceonly during development, remove before publishing
See Also¶
- interfaces composition - organizing interfaces across packages
- http servers - package structure for HTTP applications
- error handling - error conventions across package boundaries