Echoで環境変数を使い回す

2019-04-01#go

状況

Echoでデータベースに接続するときなどに環境変数を使ってデータベースのホスト名などの情報を取得したい。

問題

必要なときに都度os.Getenvで環境変数の値を取得すると、各ハンドラーで同じようなコードを何度も書くことになる。

また、環境変数が設定されていないときのデフォルト値を設定したい場合やstring以外の型に変換したい場合、さらにコード量が増えてしまう。

解決

kelseyhightower/envconfigを使って環境変数を簡単に扱えるようにし、すべてのハンドラーからカスタムコンテキストを通して環境変数にアクセスできるようにした。

// config.go
type Config struct {
  DatabaseHost     string `split_words:"true"`
  DatabaseName     string `split_words:"true"`
  DatabasePassword string `split_words:"true"`
  DatabasePort     int    `split_words:"true"`
  DatabaseUser     string `split_words:"true"`
}
// server.go
func main() {
  var config Config
  err := envconfig.Process("", &config)
  if err != nil {
    log.Fatal(err.Error())
  }
 
  // ...
}
// custom_context.go
type CustomContext struct {
  echo.Context
  Config
}
 
func CustomContextMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
  return func(c echo.Context) error {
    cc := &CustomContext{c}
    return next(cc)
  }
}
 
func ConfigMiddleware(config Config) echo.MiddlewareFunc {
  return func(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
      cc := c.(*CustomContext)
      cc.Config = config
      return next(cc)
    }
  }
}
// server.go
func main() {
  // ...
 
  e := echo.New()
  e.Use(CustomContextMiddleware)
  e.Use(ConfigMiddleware(config))
 
  // ...
}

このように実装することで、以下のように簡単に環境変数にアクセスできるようになる。

// server.go
func main() {
  // ...
 
  e.GET("/tasks", getTasks)
 
  // ...
}
 
func getTasks(c echo.Context) error {
  cc := c.(*CustomContext)
  dsn := cc.GetDSN()
 
  // ...
}
// config.go
func (c Config) GetDSN() string {
  return fmt.Sprintf(
    "%s:%s@tcp(%s:%i)/%s",
    c.DatabaseUser,
    c.DatabasePassword,
    c.DatabaseHost,
    c.DatabasePort,
    c.DatabaseName,
  )
}