# 📦 Project Layout, Modules, and Explicit Dependency Wiring

## Learning Objectives

By the end of this lesson you can:

- Lay out a Go service with `cmd/`, `internal/`, and plain packages so boundaries are enforced by the compiler.
- Keep `go.mod` honest: pin your Go version, prune unused dependencies, and know when `go mod tidy` is lying to you.
- Wire config and dependencies from a single composition root, with no `init()` magic and no package-level mutable state.
- Decide what belongs in `internal/` versus a reusable package, using a rule you can defend in code review.

## Quick Review

In the last module you built `Run(ctx, items)`: bounded by `errgroup.SetLimit`, fail-fast through `errgroup.WithContext`, and index-stable when writing results. That function was pure: it took everything it needed as arguments and returned an error. Hold onto that shape. This lesson is about making the *whole service* look like that function: inputs in, output out, no hidden reach for a global.

## Theory

### Layout: three regions, one direction of dependency

```tree
myservice/
  cmd/
    api/
      main.go
  internal/
    config/
      config.go
    store/
      store.go
    httpapi/
      server.go
      handlers.go
  go.mod
  go.sum
```

- **`cmd/api/main.go`** is the composition root. It reads config, constructs the store, constructs the HTTP server, and calls `Run`. Nothing else lives here.
- **`internal/`** is enforced by the toolchain: any package under `internal/` can only be imported by code rooted at the parent of `internal/`. That is your "not a public API" declaration, and it is checked at compile time.
- **Plain packages** outside `internal/` are things you genuinely intend others to import.

The dependency arrow points one way: `main` imports `internal/...`, and `internal/...` never imports `main` and never imports each other in a cycle. If you find `store` importing `httpapi`, you have the arrow backwards: define an interface in the consumer, not in the provider.

> [!TIP]
> Prefer few packages. A 300-line service with one `internal/app` package beats five packages with one file each. Split when a boundary earns its keep, not to look organized.

### go.mod: keep it honest

`go.mod` declares your module path, your Go version, and your direct requirements. `go.sum` pins checksums of everything, direct and transitive.

- Run `go mod tidy` after adding or deleting imports. It adds what is missing and drops what is unused.
- Commit both `go.mod` and `go.sum`. A missing `go.sum` breaks reproducible builds for everyone else.
- The `go 1.2x` line is not decoration: it sets language semantics and the default module graph behavior. Bump it deliberately.

Inline cloze check: the module path declared in `go.mod` is {{c1::the import prefix every package in the module is addressed through}}. A file under `internal/store` in module `example.com/svc` imports as {{c2::example.com/svc/internal/store}}.

### Explicit wiring: the composition root

```go
// cmd/api/main.go
func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    cfg, err := config.Load() // returns a struct, or an error; no globals
    if err != nil {
        return err
    }

    db, err := store.Open(ctx, cfg.DatabaseURL)
    if err != nil {
        return err
    }
    defer db.Close()

    srv := httpapi.NewServer(db, cfg.HTTPAddr, log.Default())
    return srv.Run(ctx)
}
```

Three properties matter here.

1. **Everything is a parameter.** `httpapi.NewServer` takes the store, the address, and a logger. It does not call `os.Getenv` and it does not import your `config` package.
2. **`run()` returns an error.** `main` does exactly one thing: exit non-zero on failure. That makes `run` testable without spawning a process.
3. **Ordering is visible.** You can read top to bottom and see that config loads before the DB opens, and that the DB closes after the server stops.

### Interfaces belong to the consumer

The server does not accept a `*store.Postgres`. It accepts the narrow interface it actually uses:

```go
// internal/httpapi/server.go
type UserStore interface {
    UserByID(ctx context.Context, id int64) (store.User, error)
}

func NewServer(users UserStore, addr string, log *slog.Logger) *Server { /* ... */ }
```

Now the HTTP package has no compile-time dependency on the database package at all, and tests can pass a fake. Notice the interface is declared next to its *use*, not next to its implementation. That is the Go idiom, and it is the opposite of the "define an interface for every struct" habit from other ecosystems.

### Config: struct in, error out

```go
type Config struct {
    HTTPAddr    string
    DatabaseURL string
}

func Load() (Config, error) {
    cfg := Config{
        HTTPAddr:    cmp.Or(os.Getenv("HTTP_ADDR"), ":8080"),
        DatabaseURL: os.Getenv("DATABASE_URL"),
    }
    if cfg.DatabaseURL == "" {
        return Config{}, errors.New("DATABASE_URL is required")
    }
    return cfg, nil
}
```

Validate once, at the edge, and fail loudly. After this point nothing in the program should read the environment again. If something needs a value, it takes it as an argument. Cloze: reading configuration once in `run()` and passing it as arguments is called {{c3::explicit dependency injection}} (or wiring at the composition root), and the alternative, package-level variables read at import time, is {{c4::global state}}.

> [!WARNING]
> Avoid `init()` for setup and package-level `var db *sql.DB`. Both hide ordering, both defeat tests, and `init()` failures surface as garbled panic stacks far from the cause.

## Worked Examples

### Example 1: Where does this helper go?

You have `func ParseCursor(s string) (int64, error)`, used by one handler.

Ask: is it used by more than one package? No. Is it part of a public API? No. Put it in `internal/httpapi/cursor.go` as an unexported function. If a second package later needs it, *then* move it out. Moving a small function is cheap; a speculative `internal/util` package is not.

### Example 2: Breaking an import cycle

`store` needs to log, and you are tempted to import `httpapi` because the logger lives there. Instead:

```go
// internal/store/store.go
type Store struct {
    db  *sql.DB
    log *slog.Logger
}

func Open(ctx context.Context, url string, log *slog.Logger) (*Store, error) { /* ... */ }
```

Pass the logger in from `main`. No cycles, no shared package, and `store` now has a testable seam.

### Example 3: Making a test-friendly boundary

In a test:

```go
type fakeUsers struct{ err error }

func (f fakeUsers) UserByID(context.Context, int64) (store.User, error) {
    return store.User{ID: 1}, f.err
}

srv := httpapi.NewServer(fakeUsers{}, ":0", slog.Default())
```

The HTTP package compiles and tests with zero database. If you cannot write this file in five lines, your constructor takes too much.

> [!NOTE]
> The `internal/` rule is enforced by `go build`, including from test files in other modules. Use it freely; it costs nothing and it documents intent.

## Discussion & Reflection

1. Your service reads `DATABASE_URL` in three places. What breaks first in tests, and what is the smallest change that removes all three?
2. A colleague adds `internal/models` imported by both `store` and `httpapi`. Is that a boundary or a shortcut? What happens when `httpapi` needs a JSON tag that `store` should not know about?
3. Which of your packages could be deleted without touching `main`? If the answer is none, is that a problem?

## Expert Corner

- **Rules of thumb.** One `cmd/` directory per binary. `internal/` for everything not intended as a public API. Constructors that return `(T, error)`, never a half-initialized struct.
- **Common mistake: constructor sprawl.** If `NewServer` takes nine arguments, group them into a `Deps` struct or accept interfaces. Nine positional parameters is where wiring starts to rot.
- **Common mistake: `go.mod` drift.** Stale requires linger until someone runs `go mod tidy`. Add it to CI as a check: run it and fail if `git diff --exit-code go.mod go.sum` is non-empty.
- **Common mistake: `internal/` treated as security.** It enforces structure, not access control. Do not put secrets behind it.
- **Rule of thumb.** This sentence is worth memorizing: ==dependencies flow through arguments, not through imports of concrete implementations==. If a package must know how another is constructed, that knowledge belongs in `main`.

## Summary & Next Steps

You now have a service skeleton: `cmd/api` owns construction and shutdown, `internal/` holds the implementation with compiler-enforced boundaries, `go.mod`/`go.sum` stay tidy, and every dependency arrives as a parameter. `run() error` keeps `main` trivial and the wiring readable top to bottom. Interfaces sit next to their consumers, so the HTTP layer never imports the database layer.

Next up: `net/http`. You will fill in `httpapi.Server` with real handlers, middleware, JSON encoding, and graceful shutdown driven by the `ctx` your `run()` already passes in. The wiring you built here is exactly what makes that server testable with `httptest`.

```flashcard
[
  { "front": "What lives in cmd/api/main.go?", "back": "Only the composition root: read config, construct dependencies, call run()." },
  { "front": "What does the internal/ directory enforce?", "back": "Packages under internal/ are importable only by code rooted at internal's parent; the compiler enforces it." },
  { "front": "Which files must be committed for reproducible builds?", "back": "Both go.mod and go.sum." },
  { "front": "What does go mod tidy do?", "back": "Adds missing requirements and removes unused ones based on imports." },
  { "front": "Where should an interface be declared in Go?", "back": "In the package that consumes it, not the package that implements it." },
  { "front": "Why should main() contain only a call to run() and log.Fatal?", "back": "run() returning an error makes startup and shutdown testable without spawning a process." },
  { "front": "What is wrong with a package-level var db *sql.DB?", "back": "It is global mutable state: hidden ordering, untestable, and initialization failures are hard to trace." },
  { "front": "How should configuration be read?", "back": "Once, at the edge (in run()), validated, then passed down as explicit arguments." },
  { "front": "What is the signature rule for constructors?", "back": "They return (T, error), never a partially initialized struct." },
  { "front": "Why avoid init() for setup?", "back": "It hides execution order and produces panic stacks far from the real cause." },
  { "front": "How do you break an import cycle where store needs a logger?", "back": "Pass the logger in from main as a constructor argument instead of importing the other package." },
  { "front": "What does the go 1.2x line in go.mod control?", "back": "Language semantics and default module graph behavior; bump it deliberately." },
  { "front": "What signals that a constructor takes too much?", "back": "Roughly nine or more positional parameters; group them into a Deps struct or accept interfaces." },
  { "front": "How can CI catch go.mod drift?", "back": "Run go mod tidy in CI and fail if go.mod or go.sum changed." }
]
```

Cloze recap: the module path in `go.mod` is {{c1::the import prefix for every package in the module}}, `internal/` boundaries are {{c5::enforced by the compiler}}, and setup logic belongs in {{c6::the composition root in cmd/}} rather than in {{c4::global state}} or `init()`.
