LoginSignup
0
0

【Go】環境ごとの設定値管理

Last updated at Posted at 2023-09-25

Go で web server を立てる際に、環境(staging とか production とか)ごとの設定値をどういうふうに管理し、アクセス可能にするかのメモ。

外からの値の受け取り方としては環境変数から取るか、コマンドラインフラグで渡すかの2択だけど、どちらを取ったとしてもアプリケーションコードのロジック中からその値をどのように参照するかという話がある。
毎回 os.Getenv とか書きたくないし、このアプリケーションにどのような設定を入れられるのかがわかりづらいので、どこかで一元管理したい。

そこで、以下のように設定管理用の myconfig というパッケージを作っておいて、そこに集約させてみる。
設定値を持った構造体をパッケージグローバル変数に突っ込んでおいて、その構造体経由で設定値を参照できるようにする。

myconfig.go
package myconfig

import (
    "github.com/caarlos0/env/v6"
    /*...以下省略*/
)

var cfg config

type config struct {
	DBName       string `env:"DB_NAME" envDefault:"hoge_test"`
	DBUser       string `env:"DB_USER" envDefault:"root"`
	DBPass       string `env:"DB_PASS" envDefault:"password"`
	DBHost       string `env:"DB_HOST" envDefault:"localhost"`
	DBPort       int    `env:"DB_PORT" envDefault:"3306"`
}

// パッケージ初期化時に一度だけ環境変数をロードし、cfg 変数にセットします
func init() {
    if err := env.Parse(&cfg); err != nil {
		panic(fmt.Sprintf("failed to setup config, error: %s", err))
	}
}

func DBName() string {
	return cfg.DBName
}

func DBUser() string {
	return cfg.DBUser
}

func DBPass() string {
	return cfg.DBPass
}

func DBHost() string {
	return cfg.DBHost
}

func DBPort() int {
	return cfg.DBPort
}

値を使う側はこんな感じで、myconfig パッケージに用意されたゲッター経由でアプリケーションのどこからでもアクセスできる。

main.go
func main() {
    fmt.Println(myconfig.DBName())
}

値は全て環境変数から取る前提である。
一応ポイントというか解説というか

  • 環境変数から値を取り出す処理は ライブラリを使って楽している
    • ライブラリに依存したくない場合は、自分で書いても全然良い
    • フィールドを export しているのはライブラリから参照させるためなので、自分で書く場合はフィールドも unexported にして良い
  • init 関数で初期化し、環境変数から取得した値をセットした構造体を作って、グローバル変数に入れておく
    • init 関数ではなく自前の initialize 関数を用意して明示的に呼ぶ形でも良い
      • その場合、シングルトンパターンで config 構造体の初期化は一度のみに制限するなどの工夫をした方がいいかも
  • ゲッターのみを提供することで、不用意に設定値を変えてしまうリスクをなくす
  • config 構造体のフィールドで設定値を一元管理しているので、どのような値を設定できるのかの確認がしやすい

ローカル開発用に .env ファイルなどを使いたい場合

こんな感じで、.env と併用させることもできる。
.env ファイルがあるときはそれを読み込んで値をオーバーライドするようにしておく。
(.env ファイルへのパスは適宜合わせる)

myconfig.go
package myconfig

import (
    "github.com/joho/godotenv"
    /*...以下省略*/
)

var cfg config

type config struct {/*...省略*/}

// パッケージ初期化時に一度だけ環境変数をロードし、cfg 変数にセットします
func init() {
	// 開発環境以外では .env を使用しないのでここのエラーは無視します
	_, b, _, _ := runtime.Caller(0)
	_ = godotenv.Load(filepath.Join(filepath.Dir(b), "../.env_dev"))

    if err := env.Parse(&cfg); err != nil {
		panic(fmt.Sprintf("failed to setup config, error: %s", err))
	}
}

flag と環境変数を併存させたい場合

あまりないかもだが、flag と環境変数を併存させてみる。この場合、両方設定されているときにどちらの値を優先的に評価するかを決めておく必要がある。
この例では、flag の値を優先している。

myconfig.go
type config struct {
	WriterDBName       *string
}

func init() {
    cfg = config {
        WriterDBName: flag.String("DB_NAME", getEnvString("DB_NAME", "hoge_test"), "db name")
        /*...省略*/
    }
}

func getEnvString(envKey string, defaultVal string) string {
	if envVal, isSet := os.LookupEnv(envKey); isSet {
		return envVal
	}
	return defaultVal
}
  • flag.String の第二引数が flag が設定されていない時のデフォルト値
    • その値を環境変数から値が取れればその値を、取れなければ引数に与えたデフォルト値を返すようにしている
  • flag > 環境変数 > デフォルト値、という順で評価されるようにしている
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0