アプリケーションの設定値を環境変数から注入していますか?
Goの場合、structに設定値を集約するのには GitHub - caarlos0/env: A simple and zero-dependencies library to parse environment variables into structs. などのライブラリが使えると思います。一方でこういうライブラリでstructをネストさせていくと、結局どの環境変数が必要なのかざっと眺めるのが難しいです。
type Config struct { Port int `env:"PORT" envDefault:"8080"` DatabaseDSN string `env:"DATABASE_DSN,required"` // 必須 Nested NestedConfig `envPrefix:"NESTED_"` } type NestedConfig struct { DSN string `env:"DSN,required"` // 必須 }
↑これぐらいならいいけど実際にはもっと設定項目があるはず。NESTED_DSN
という環境変数をセットする必要があるのだけど、↑のような実装だとアプリケーションコードを NESTED_DSN
でgrepしてもヒットしない!!
ということでリフレクションで解決しましょう。
package main import ( "fmt" "reflect" "strings" "golang.org/x/exp/slices" ) type Config struct { Port int `env:"PORT" envDefault:"8080"` DatabaseDSN string `env:"DATABASE_DSN,required"` Nested NestedConfig `envPrefix:"NESTED_"` } type NestedConfig struct { DSN string `env:"DSN"` } func main() { // アプリケーションで使う設定値用のstructを渡す cfg := Config{} envVars := aggregateEnvVars(cfg, "", "") for _, ev := range envVars { fmt.Printf("%s: %s", ev.Name, ev.FieldPath) if ev.Required { fmt.Print(" (required)") } fmt.Println() } } type envVar struct { FieldPath string Name string Required bool } func aggregateEnvVars(cfg any, envPrefix, fieldPath string) []envVar { st := reflect.ValueOf(cfg) var varNames []envVar for i := 0; i < st.NumField(); i++ { field := st.Field(i) // skip unexported field if !field.CanInterface() { continue } fieldTy := st.Type().Field(i) // envPrefix tagがあるならstruct型のフィールドなので再帰的にたどる newEnvPrefix := fieldTy.Tag.Get("envPrefix") if newEnvPrefix != "" { varNames = append(varNames, aggregateEnvVars(field.Interface(), envPrefix+newEnvPrefix, fieldPath+"."+fieldTy.Name)...) continue } // env:"AAA,required" envTag := fieldTy.Tag.Get("env") if envTag == "" { continue } xs := strings.Split(envTag, ",") varName := xs[0] required := slices.Contains(xs, "required") varNames = append(varNames, envVar{ FieldPath: fieldPath+"."+fieldTy.Name, Name: envPrefix + varName, Required: required, }) } return varNames }
実行すると以下のような出力が得られます。どのフィールドの値がどの環境変数に対応するのか一目で分かって便利ですね。structのフィールドにコメントを書いていたらそれも出力されてほしいけど、それをやるには静的解析が必要な予感がします。
PORT: .Port DATABASE_DSN: .DatabaseDSN (required) NESTED_DSN: .Nested.DSN