golang-spf13-viper について
Go エンジニア向けの spf13/viper ガイドです。フラグ、環境変数、ファイル、リモートのキー・バリューソース、デフォルト値を横断して設定を解決します。優先順位、バインド、デコード、再読み込み、テスト分離を解説します。
主な機能
- 01階層的な設定解決
- 02環境変数とフラグのバインド
- 03設定ファイルの読み込みと統合
- 04mapstructure による構造体のデコード
- 05ファイル変更の監視
- 06設定テストの分離
活用シーン
- 01CLI フラグと環境設定の統合
- 02ファイルからのサービス設定読み込み
- 03ファイル変更後の設定再読み込み
- 04テスト間の設定状態漏れの防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-spf13-viperインストール後、Claude Code で/golang-spf13-viperを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/binding-and-env.mdsources-and-formats.mdtesting-and-isolation.mdunmarshal.mdwatch-and-reload.md詳細
ペルソナ: 設定を階層化されたシステムとして扱う Go エンジニアです。フラグが環境変数より優先され、環境変数がファイルより優先され、ファイルがデフォルト値より優先されます。また、4 つのレイヤーすべてに 1 つの API からアクセスできるよう、すべてのキーをバインドします。
Go で階層化された設定に spf13/viper を使用する
Viper は、固定された優先順位に従って複数のソースから設定値を解決します。ユーザー向けの機能はなく、コマンドやフラグも定義しません。Viper の役割は、優先度の高いものから低いものへソースのレイヤーをたどり、「キー X の現在の値は何か?」に答えることです。
公式リソース:
このスキルは網羅的なものではありません。詳細については、ライブラリのドキュメントやコード例を参照してください。
- Go パッケージのドキュメント、シンボル、バージョン、インポーター、既知の脆弱性については、Context7 より優先して
samber/cc-skills-golang@golang-pkg-go-devスキル(godig)を参照してください。 - ライブラリの使用箇所(定義、呼び出し箇所、診断)をコード内で調べるには、
samber/cc-skills-golang@golang-goplsスキル(gopls)を参照してください。 - pkg.go.dev にインデックスされていないドキュメントについては、引き続き Context7 をフォールバックとして利用できます。
go get github.com/spf13/viper@latest
Viper と cobra の違い
Cobra はサブコマンド、フラグ、引数の検証、補完など、コマンドツリーを管理します。Viper は設定の解決を管理します。独自のユーザー向け機能はなく、ソースのレイヤーをたどって「キー X の値は何か?」に答える、純粋なキーと値のリゾルバーです。
- Cobra のみ — フラグのみの CLI。
- Viper のみ — 設定ファイルを使用するデーモン。
- 両方 —
PersistentPreRunEでBindPFlagを使ってフラグをバインドします。
→ この連携における cobra 側については、samber/cc-skills-golang@golang-spf13-cobra を参照してください。
優先順位のパイプライン
Viper は次の順序でソースをたどってキーを解決します(最初に設定された値が使われます)。
1. explicit Set() — viper.Set("key", val) highest priority
2. flag — bound pflag.Flag
3. env var — BindEnv / AutomaticEnv
4. config file — ReadInConfig / MergeInConfig
5. KV remote — etcd / Consul
6. default — viper.SetDefault("key", val) lowest priority
このパイプラインの順序は固定されており、変更できません。これを理解しておくと、viper のバグの大半を防げます。設定ファイルから取得される「はず」のキーが、環境変数やデフォルト値を持つフラグによって上書きされることがあります。
ソースと設定ファイル
viper.SetConfigName("config")
viper.AddConfigPath("$HOME/.myapp")
if err := viper.ReadInConfig(); err != nil {
var notFound *viper.ConfigFileNotFoundError
if !errors.As(err, ¬Found) {
return fmt.Errorf("reading config: %w", err) // propagate real errors only
}
}
ConfigFileNotFoundError は適切に処理する必要があります。設定ファイルは通常、任意だからです。ファイルが見つからない場合のエラーを処理しないと、フラグや環境変数だけで実行しても問題のないプログラムがクラッシュします。
サポートされる形式(JSON、TOML、YAML、HCL、INI、properties)、MergeInConfig、リモート KV については、sources-and-formats.md を参照してください。
環境変数のバインドとキーの置換
viper で最もバグが発生しやすい領域です。3 つの設定すべてを連携させる必要があります。どれか 1 つでも欠けると、ネストされたキーの解決に失敗します。
// ✓ Good — all three wired together at startup
viper.SetEnvPrefix("MYAPP") // prevent collisions: PORT → MYAPP_PORT
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // database.host → MYAPP_DATABASE_HOST
viper.AutomaticEnv()
// ✗ Bad — without SetEnvKeyReplacer, viper looks for MYAPP_DATABASE.HOST (dot preserved)
BindEnv、AllowEmptyEnv、環境変数とデフォルト値の相互作用については、binding-and-env.md を参照してください。
フラグのバインド(cobra との接続点)
cobra のフラグは init() または PersistentPreRunE で Viper にバインドしてください。RunE では決してバインドしないでください(PersistentPreRunE ですでに設定の読み込みが行われてから RunE が実行されるため、RunE で設定したバインドは反映されません)。
func init() {
rootCmd.PersistentFlags().Int("port", 8080, "listen port")
viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
// viper.BindPFlags(cmd.Flags()) — bind an entire FlagSet at once
}
AllowEmptyEnv とフラグ・環境変数の相互作用の詳細については、binding-and-env.md を参照してください。
構造体へのアンマーシャリング
viper.Unmarshal は mapstructure を使って、解決済みの設定を構造体にマッピングします。
type Config struct {
Port int `mapstructure:"port"`
Database struct {
MaxConn int `mapstructure:"max_conn"` // explicit tag: mapstructure won't convert underscore→camelCase
} `mapstructure:"database"`
}
var cfg Config
viper.Unmarshal(&cfg)
必ず mapstructure タグを使用してください。 暗黙的なマッピングは、ネストされた構造体やアンダースコアを含むフィールドでは不安定です。Sub("database").Unmarshal よりも UnmarshalKey("database", &dbCfg) を優先してください。キーが存在しない場合に Sub で必要となる nil チェックを避けられます。
time.Duration、net.IP、スライスのデコーダー、およびカスタム DecodeHook の登録については、unmarshal.md を参照してください。
サブツリー
viper.Sub("database") は、指定したプレフィックスにスコープを限定した新しい *viper.Viper を返します。キーが存在しない場合は nil を返すため、戻り値のメソッドを呼び出す前に必ず nil チェックを行ってください。nil のリスクを完全に避けられる UnmarshalKey("database", &dbCfg) を優先してください。
ホットリロード
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) { /* re-apply changed values */ })
WatchConfig は fsnotify を使用して inode を監視するため、rename によるアトミックな書き込みを行うエディター(vim、neovim)では inode が置き換えられ、コールバックが実行されない場合があります。ホットリロードのテストには、エディターでの保存ではなく echo >> config.yaml を使用してください。競合状態に安全なリロードパターンについては、watch-and-reload.md を参照してください。
テストの分離
テストではグローバルの viper を絶対に使用しないでください。状態がテストケース間で漏れます。各インスタンスを分離するため、テストごとに viper.New() を使用してください。
v := viper.New()
v.SetConfigFile("testdata/config.yaml")
require.NoError(t, v.ReadInConfig())
t.Setenv との相互作用や Reset() の制限については、testing-and-isolation.md を参照してください。
ベストプラクティス
- プレフィックス、キー置換関数、AutomaticEnv をまとめて設定する — いずれかが欠けると、ネストされた環境変数キーが解決されずに無視されます(
database.host→DATABASE.HOSTとなり、DATABASE_HOSTになりません)。 ConfigFileNotFoundErrorを適切に処理する — フラグと環境変数だけで動作するサービスが、設定ファイルがないことでクラッシュしてはいけません。- 設定用構造体には必ず
mapstructureタグを使用する — 暗黙のマッピングでは、ネストされたフィールドやアンダースコアを含むフィールドが暗黙に見落とされます。 - テストではグローバルではなく、必ず
viper.New()を使用する — グローバルにはテスト実行間で状態が蓄積しますが、テストごとのインスタンスは分離されています。 Execute()の前にフラグをバインドする —RunE内でのバインドは遅すぎます。cobra はRunEの実行前にフラグを解析します。
よくある間違い
| 間違い | 失敗する理由 | 修正方法 |
|---|---|---|
SetEnvKeyReplacer を設定せずに AutomaticEnv を使用する | database.host は MYAPP_DATABASE.HOST(ドットがそのまま)を探すため、一致しません | AutomaticEnv の前に SetEnvKeyReplacer(strings.NewReplacer(".", "_")) を追加する |
構造体フィールドに mapstructure タグがない | ネストされたフィールドやアンダースコアを含むフィールドが暗黙に見落とされる | すべてのフィールドに mapstructure:"key_name" を追加する |
| テストでグローバルの viper を使用する | あるテストの状態が次のテストを汚染し、実行順序によって不安定になる | テストごとに viper.New() を作成する |
ConfigFileNotFoundError の確認がない | フラグと環境変数だけで動作すべきサービスが、設定ファイルがないことでクラッシュする | errors.As(err, ¬Found) を使用し、見つからない場合以外のエラーのみを伝播させる |
参考資料
- sources-and-formats.md — 対応するファイル形式、複数パスの検索、MergeInConfig、リモート KV(etcd/Consul)
- binding-and-env.md — BindEnv、AutomaticEnv、SetEnvPrefix、SetEnvKeyReplacer、AllowEmptyEnv、タイミングに関するルール
- unmarshal.md — Unmarshal、UnmarshalKey、mapstructure タグ、カスタム DecodeHooks(Duration、IP、スライス)
- watch-and-reload.md — WatchConfig、OnConfigChange、fsnotify の注意点、アトミック rename の落とし穴、競合状態に安全なパターン
- testing-and-isolation.md — テストごとの viper.New()、t.Setenv との相互作用、Reset() の制限、スナップショットと復元
関連項目
- → CLI アーキテクチャ全般(プロジェクト構成、終了コード、シグナル処理、cobra と viper の連携)については、
samber/cc-skills-golang@golang-cliスキルを参照してください - → この連携における cobra 側(フラグの定義とバインド)については、
samber/cc-skills-golang@golang-spf13-cobraスキルを参照してください - → Go のテストパターン全般については、
samber/cc-skills-golang@golang-testingスキルを参照してください
spf13/viper でバグや予期しない動作を見つけた場合は、https://github.com/spf13/viper/issues で issue を作成してください。