Case Studies Best Practices
A condensed summary of the 25 most important habits for reading, applying, and authoring production Go case studies drawn from every page in this section.
-
State the operational contract first: List SLOs, deploy target, and dependencies before copying code from a reference build so patterns map to your constraints.
-
Trace vertical slices: Follow request, data, and deploy flows end to end instead of reading packages in alphabetical order.
-
Extract decisions, not folder trees: Record boundaries and observability contracts in ADRs; avoid cargo-culting module paths from another org.
-
Separate liveness and readiness: Health endpoints must differ so orchestrators drain traffic only when dependencies fail, not when the process is merely busy.
-
Keep cmd entrypoints thin:
mainshould wire config, observability, and servers while business rules live ininternal/serviceandinternal/store. -
Set explicit HTTP server timeouts:
ReadHeaderTimeout, read/write timeouts, and idle timeouts prevent slowloris and hung clients from accumulating goroutines. -
Tune database/sql pools: Set
MaxOpenConns,MaxIdleConns, andConnMaxLifetimefrom instance size and database limits before chasing encoder micro-optimizations. -
Propagate context per query: Apply
context.WithTimeouton each SQL call so one slow statement cannot inherit a vague global deadline only. -
Correlate logs and traces: Inject request IDs and trace IDs into
sloghandlers so incidents join metrics, logs, and spans without manual grep gymnastics. -
Enforce gRPC backpressure: Return
ResourceExhaustedor shed load when subscriber buffers fill instead of letting in-memory buses grow without bound. -
Compile gRPC modules once: Reuse
CompiledModuleor equivalent server warm-up paths; avoid per-request recompilation on hot paths. -
Bind pprof to admin interfaces: Never expose
debug/pprofon public listeners; capture profiles during controlled load tests only. -
Layer cobra and viper cleanly: Load config in
PersistentPreRunE, passcmd.Context()into IO, and keep command packages free of SQL or SMTP details. -
Guard write-mode CLI commands: Require
--yesor interactive confirmation for destructive subcommands when stdin is not a TTY. -
Patch operator status only: Reconcilers update
statussubresources and conditions; avoid fighting users by mutatingspecexcept via webhooks. -
Set owner references on operands: Kubernetes child objects need controller references so deletes garbage-collect Deployments and related resources.
-
Run envtest before live clusters: Controller and webhook logic should pass fast envtest suites on every PR without minikube cost.
-
Cap WASM guest capabilities: WASI hosts should allow-list syscalls, bound memory, and enforce per-invocation deadlines for untrusted modules.
-
Version host-guest ABIs: Smoke tests must assert export names and ABI metadata so TinyGo upgrades do not silently break wazero hosts.
-
Characterize before refactor: Lock HTTP golden responses and critical service behavior tests before splitting a god package.
-
Split store before handlers when SQL is tangled: Moving queries behind interfaces first removes the worst coupling in large
internal/apppackages. -
Count queries per request: N+1 patterns often hide as many fast child spans; fix SQL shape before swapping JSON libraries.
-
Benchmark with
-benchmem: Trackallocs/opon handler hot paths and fail CI on regressions, not only wall-clock ns/op. -
Profile under realistic load: Capture CPU and alloc profiles during sustained RPS; idle profiles mislead optimization priorities.
-
Revisit stories after toolchain upgrades: Re-benchmark and re-read operator/webhook compatibility when Go minors, gRPC, or controller-runtime versions change.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).