Lesson 24 / 26

Project Structure and Dependencies

Start simple, grow deliberately.

cmd, internal and small packages

Start with a flat package and split only when it helps. Common layouts put entry points in cmd/<app>/main.go and private code in internal/ packages organised by domain (orders, payments), not by layer type. Keep main thin: parse configuration, build dependencies, start servers. Pass dependencies explicitly (constructor functions) rather than using globals. Add third-party modules sparingly, keep go.mod tidy, and check vulnerabilities with govulncheck.

From code to production

Structure projects, shut down gracefully, and review code with a checklist.

Three ideas: project structure, graceful shutdown and observability, checklist.
Figure 8.1 — Structure, shutdown and checklist.

Wiring in main (sketch)

A thin main that builds dependencies explicitly. Not run here.

func main() {
	cfg := config.FromEnv()
	db, err := sql.Open("pgx", cfg.DatabaseURL)
	if err != nil {
		log.Fatal(err)
	}
	orders := orders.NewService(orders.NewStore(db))
	srv := &http.Server{
		Addr:              ":8080",
		Handler:           api.Routes(orders),
		ReadHeaderTimeout: 5 * time.Second,
	}
	log.Fatal(srv.ListenAndServe())
}

Run govulncheck

govulncheck reports known vulnerabilities only in code paths your program actually calls.

Quick check: Where do Go projects commonly put private application packages?

  • In go.sum
  • Under public/
  • Under internal/
  • Inside the GOPATH bin folder
Answer

Under internal/ — internal/ cannot be imported by other modules.