11. Packages and modules
11.1 Visibility is capitalization
There's no public/private keyword. An identifier starting with an
uppercase letter is exported (visible outside the package); lowercase is
package-private. This single rule replaces access modifiers entirely.
11.2 One package per directory, package name ≠ import path segment always
myapp/
go.mod module github.com/kumaran/myapp
config/
config.go package config
storage/
storage.go package storage
memory.go package storage (same dir, same package)
server/
server.go package server
internal/
auth/
auth.go package auth — importable ONLY from within myapp
internal/ is a compiler-enforced convention: any package under a
directory named internal can only be imported by code rooted at the
parent of that internal directory. It's Go's answer to "private to
this module, but shared across several of its own packages."
11.3 init() and import cycles
Every package can define one or more init() functions, run automatically
before main(), in dependency order — used for registering drivers
(database/sql drivers register themselves this way) but easy to overuse
into "spooky action at a distance." Prefer explicit initialization
(a New() constructor) unless you're building a plugin-registration
pattern. Go also forbids import cycles at compile time — if package a
imports b and b imports a, the build fails immediately, forcing you
to extract the shared piece into a third package.
11.4 Real-world example
Day 9's refactor of the Week 1 project into config/, storage/,
server/, health/ packages is the shape of nearly every non-trivial Go
service: each package owns one responsibility, server depends on
config and storage but not vice versa, and the dependency graph is a
DAG you can read straight off the import statements — no hidden coupling
through global state.