Stage 7 · Master
Phase 1 — Project Foundation
Dependency Injection
Wire configuration and the HTTP server together by hand in one composition root, and decide explicitly why this course skips wire and fx.
Dependency Injection Is a Pattern, Not a Library
Dependency injection means a piece of code receives the things it depends on as parameters or fields, rather than constructing or looking them up itself. server.New already does this — it receives an address string rather than reading ORGANIZATION_PORT from the environment directly. Nothing about that requires a framework; it requires a discipline about where construction happens, which this lesson makes explicit and extends to graceful shutdown.
cmd/api/main.go Is the Only Composition Root
A composition root is the one place in a program allowed to know about every concrete type and wire them together. Every internal package this service will ever have — config, server, and later model, repository, service, handler — gets constructed, directly or indirectly, from cmd/api/main.go. No internal package is allowed to construct another internal package's concrete dependencies internally; each one declares what it needs through its constructor's parameters instead.
package server
import (
"context"
"net/http"
"os/signal"
"syscall"
"time"
)
// Run starts srv and blocks until an interrupt or termination signal is
// received, then shuts down within the given grace period. It returns nil
// only after a clean shutdown.
func Run(srv *http.Server, shutdownGrace time.Duration) error {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
errCh := make(chan error, 1)
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
errCh <- err
}
}()
select {
case err := <-errCh:
return err
case <-ctx.Done():
}
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGrace)
defer cancel()
return srv.Shutdown(shutdownCtx)
}
import (
"fmt"
"log"
+ "time"
...
)
srv := server.New(fmt.Sprintf(":%d", cfg.Port))
log.Printf("organization service (%s) listening on %s", cfg.Env, srv.Addr)
- if err := srv.ListenAndServe(); err != nil {
+ if err := server.Run(srv, 10*time.Second); err != nil {
log.Fatalf("server stopped: %v", err)
}
+ log.Println("organization service shut down cleanly")
}One import, one changed call, one added log line. ListenAndServe blocks until the process is killed; server.Run blocks until a signal arrives and then gives in-flight requests ten seconds to finish. That is the whole behavioural difference, and it is why this file — not the server package — is the only place that decides how long shutdown is allowed to take.
A Constructor's Signature Is the Real Interface
server.New(addr string) *http.Server states, in its signature alone, everything the server package depends on today: one string. When Phase 2's Service Bootstrap lesson adds a database connection pool, that dependency will show up as a new parameter on server.New — a compile error at every call site until main.go is updated. That compile-time visibility is the entire value of manual dependency injection: a missing dependency is caught by the type checker, not discovered at runtime.
Why This Course Skips wire and fx
| Approach | How a missing dependency fails | Cost |
|---|---|---|
| Manual (this course) | Compile error — the constructor call site won't type-check | A little more typing at each composition root |
| google/wire | Compile error too, but only after running code generation | An extra build step and generated files to keep in sync |
| uber-go/fx | Runtime panic at application start, often with a dependency-graph error message | No extra build step, but failures are only visible when the app actually boots |
For a handful of services with a small, mostly-linear dependency graph — config, a database pool, a logger, a server — the code-generation overhead of wire and the runtime-discovery risk of fx both cost more than they save. Manual composition roots stay small and readable at this scale; the course revisits this decision explicitly if a later phase's dependency graph ever grows enough to justify it.
Confirming Graceful Shutdown Actually Happens
Applied exercise
Simulate a slow shutdown and watch the grace period matter
The 10-second grace period in server.Run does nothing observable yet, because nothing holds an in-flight request open. Make it observable.
- Temporarily add a route to server.New's mux that calls time.Sleep(15 * time.Second) before responding.
- Start the service, curl the slow route in one terminal, then send SIGINT from another terminal while the request is still in flight.
- Observe that Shutdown waits for the in-flight request up to the grace period, then confirm whether the curl request completed or was cut off.
- Remove the temporary slow route — it was only for observing shutdown behavior.
Deliverable
A short note describing whether the in-flight curl request completed successfully before the process exited, and why.
Completion checks
- The temporary route is fully removed afterward; git status shows no leftover changes in server.go.
- The note correctly explains that srv.Shutdown waits for in-flight requests up to shutdownGrace, rather than cutting them off immediately.
Why does this course use manual dependency injection instead of google/wire or uber-go/fx?
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.