What's in your main-dot-go? (aka Go Project boilerplate)
Sometimes I write small services in Go from scratch. And every time main.go
ends up looking almost the same:
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigs := make(chan os.Signal, 2)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigs
signal.Stop(sigs)
cancel()
}()
if err := run(ctx, os.Args[1:]); err != nil {
log.Fatalln(err)
}
}
type Config struct {
HTTPAddr string
HTTPShutdownTimeout time.Duration
}
func run(ctx context.Context, args []string) error {
flags := flag.NewFlagSet("", flag.ExitOnError)
var conf Config
flags.StringVar(&conf.HTTPAddr, "http-addr", "127.0.0.1:10080", "address to listen on")
flags.DurationVar(&conf.HTTPShutdownTimeout, "http-shutdown-timeout", 5*time.Second, "server shutdown timeout")
if err := flags.Parse(args); err != nil {
return err
}
// TODO: define the handler, the routing, and wire the dependencies with the main context
mux := http.NewServeMux()
server := &http.Server{
Addr: conf.HTTPAddr,
Handler: mux,
}
errs := make(chan error, 1)
go func() {
log.Printf("starting: addr %s", server.Addr)
errs <- server.ListenAndServe()
}()
select {
case <-ctx.Done():
log.Println("exiting...")
case err := <-errs:
return err
}
// create new context because top-most one is already canceled
ctx, cancel := context.WithTimeout(context.Background(), conf.HTTPShutdownTimeout)
defer cancel()
return server.Shutdown(ctx)
}
Of course, not every service requires an HTTP server, but the general idea stands.
When Go will introduce signal.NotifyContext()
, the signals handling in main()
function will be much smaller (we’re at go1.15rc1 as I’m writing that and the change hasn’t landed into the release yet).
I love how transparent is the flow here and how everything is scoped inside run()
function. This structure forces you to eliminate global state, making unit or integration testing almost trivial — at least, in theory ;)
It might feel like too much of boilerplate code for a “small” service. In practice, though, I don’t recall any time this caused any real troubles to me. The beauty of Go is in explicitness.