Summary of How I write HTTP services in Go after 13 years:

  • The NewServer constructor - big constructor that takes all dependencies as parameters. returns http.Handler. configures its own muxer and calls out to routes.go. includes setting up middleware, CORS, logging. ** Long argument lists - type safety in arguments.
  • Map the entire API surface in routes.go
  • func main() only calls run() - run() returns an error and accepts OS fundamentals as parameters for easier testing. background context is created in main but all other context handling is done in run. ** Gracefully shutting down - pass context through and check it at each level. ** Controlling the environment - use flags.NewFlagSet inside run. make a replacement os.Getenv for tests and pass that to run (or the real thing for main).
  • Maker funcs return the handler - creates a readable closure for each.
  • Handle decoding/encoding in one place - for JSON/XML encode/decode.
  • Validating data - single method interface with Valid(ctx context.Context) (problems map[string]string)
  • The adapter pattern for middleware - take an http.Handler and return one.
  • Sometimes I return the middleware ** An opportunity to hide the request/response types away - keeps global space clear.
  • Use inline request/response types for additional storytelling in tests
  • sync.Once to defer setup - anything expensive during handler setup, defer to the first time it’s called to improve overall app startup time.
  • Designing for testability ** What is the unit when unit testing? - multiple levels of options from run() down to calling individual handler functions. ** Testing with the run function ** Waiting for readiness - loop on hitting a /healthz endpoint until the server has started.

golang