はじめに
あるGoアプリケーションでぼちぼち設定値が増えてきたのでリファクタリングしたのでメモ。以下が達成したかったこと。
- 設定値の一覧性
- 読み出し時のシンプルさ
- 環境変数での設定値インジェクション
動的な値の更新は対応しない。今回の要件では再デプロイでの設定値更新で十分であったため。
全体像
全体像 https://github.com/yoshikipom/go/tree/main/config
. ├── config │ └── config.yml // 設定ファイル ├── go.mod ├── go.sum ├── internal │ └── config │ └── config.go // 設定ファイルと環境変数からgoの構造体へのマッピング └── main.go // テスト用
使い方
config.yml
port: 8080 key: value
アプリケーション内で一度だけ Initialize
関数を呼ぶ。
エラーが出る可能性があるのはInitialize
のみにできるのであとは使いたい場所でGetConfig
を呼ぶだけ。
使っているライブラリはconfig
パッケージに隠蔽されているのでライブラリの変更は安全に行える。
単体テストでテスト用のファイルを渡したくなることがあったので、 Initialize
関数で設定ファイルのパスを渡すことにした。
main.go
package main import ( "fmt" "github.com/yoshikipom/go/config/internal/config" ) func main() { // call only once err := config.Initialize("./config/config.yml") if err != nil { panic(err) } // you can get config anywhere c := config.GetConfig() fmt.Printf("%+v", c) }
実行例
# config.ymlの値が読めている yoshiki@yoshiki-mbp:go/config ‹main*›$ go run "/Users/yoshiki/work/study/go/config/main.go" &{Port:8080 Key:value}% # 環境変数で渡した"new_value"が読み込めている。(key= でなく KEY= の理由は後述。) yoshiki@yoshiki-mbp:go/config ‹main*›$ KEY=new_value go run "/Users/yoshiki/work/study/go/config/main.go" &{Port:8080 Key:new_value}%
config.goの実装
package config import ( "github.com/spf13/viper" ) var config *MyConfig type MyConfig struct { Port int `mapstructure:"port"` Key string `mapstructure:"key"` } func Initialize(fileName string) error { viper.SetConfigFile(fileName) if err := viper.ReadInConfig(); err != nil { return err } viper.AutomaticEnv() c := &MyConfig{} if err := viper.Unmarshal(c); err != nil { return err } config = c return nil } func GetConfig() *MyConfig { if config == nil { panic("config is not initialized.") } return config }
- Viper というライブラリを利用
おわりに
使いやすい設定値管理ができてよかった。Viperはシンプルかつ高機能で良い。