巧用 viper 實現多環境配置
巧用viper實現多環境配置
應用系統的配置資訊,一般分為兩種,一是經常變化的配置則儲存到資料庫,另外一種則是不常變化的則以配置檔案的形式儲存。一般而言,應用系統至少有三組執行環境:開發環境、測試環境、生產環境,本文主要探討在golang
專案中如何使用viper實現多環境應用配置。如果對viper不瞭解的可以先去閱讀一下官方說明ofollow,noindex" target="_blank">https://github.com/spf13/viper
。
青銅時代
一開始的時候,我針對每個環境配置各自的yaml,在啟動的時候根據環境變數讀取相應的配置檔案,處理程式碼如下:
func initConfig() (err error) { env := os.Getenv("GO_ENV") viper.SetConfigName(env) viper.AddConfigPath("./configs") viper.SetConfigType("yml") err = viper.ReadInConfig() return } func main() { err := initConfig() if err != nil { panic(err) } fmt.Println(viper.GetString("db.uri")) fmt.Println(viper.GetString("db.poolSize")) }
不同環境的配置檔案如下:
# test app: viper-test db: uri: postgres://tree:[email protected]:5432/viper-test?connect_timeout=5&sslmode=disable poolSize: 100
# production app: viper-test db: uri: postgres://tree:[email protected]:5432/viper?connect_timeout=5&sslmode=disable poolSize: 100
由上面的程式碼可以看出,因為viper讀取的配置只有一份,因此需要在每個配置中將所有的配置都一一填寫,而不同環境的配置絕大部分都是相同的,只有小部分是不一致。一開始只有不到10個配置項的時候還好維護,後面配置資訊越來越多,幾十個的時候就是一個深坑了,看到眼都花了,太難管理。
白銀時代
各執行環境中的配置90%左右是相同,而剩下的10%才是各環境的差異配置,是否可以將相同的配置以預設值的形式儲存,而各環境與預設值不相同的再覆蓋呢?查看了一下文件,發現了viper.SetDefault
的函式,一開始是直接在程式碼一行行的把預設配置寫上,但是這樣無法利用yaml的便利,在研究了相關的程式碼之後,最後調整為如下的處理形式,程式碼如下:
func initConfig() (err error) { configType := "yml" defaultPath := "./configs" v := viper.New() // 從default中讀取預設的配置 v.SetConfigName("default") v.AddConfigPath(defaultPath) v.SetConfigType(configType) err = v.ReadInConfig() if err != nil { return } configs := v.AllSettings() // 將default中的配置全部以預設配置寫入 for k, v := range configs { viper.SetDefault(k, v) } env := os.Getenv("GO_ENV") // 根據配置的env讀取相應的配置資訊 if env != "" { viper.SetConfigName(env) viper.AddConfigPath(defaultPath) viper.SetConfigType(configType) err = viper.ReadInConfig() if err != nil { return } } return }
此函式將default.yml
的所有配置讀取至一個新的viper例項中,再以SetDefault
將所有配置寫入為預設配置,而各環境配置檔案只需要配置差異部分,配置如下:
# default app: viper-test db: uri: postgres://tree:[email protected]:5432/viper-test?connect_timeout=5&sslmode=disable poolSize: 100
# test與default完全一致,為空檔案
# production只是資料庫連線串不一致,只需要配置此項 db: uri: postgres://tree:[email protected]:5432/viper?connect_timeout=5&sslmode=disable
通過此調整,不再需要重複的維護相同的配置項,而且也能直觀的看出各執行環境的配置差異,減少配置資訊的出錯概率。
王者時代
因為主要是後端程式應用,程式交付一般都是通過docker映象的形式,配置檔案與編譯後的應用程式一起打包至映象中,在多個專案中也使用得挺順暢。最近有一個專案非執行在docker環境下,因此希望能將配置檔案一起打包至應用程式的方式,在瞭解了幾個相關的專案,最終選擇了使用packr 來將配置檔案打包,調整之後的程式碼如下:
func initConfig() (err error) { box := packr.NewBox("./configs") configType := "yml" defaultConfig := box.Bytes("default.yml") v := viper.New() v.SetConfigType(configType) err = v.ReadConfig(bytes.NewReader(defaultConfig)) if err != nil { return } configs := v.AllSettings() // 將default中的配置全部以預設配置寫入 for k, v := range configs { viper.SetDefault(k, v) } env := os.Getenv("GO_ENV") // 根據配置的env讀取相應的配置資訊 if env != "" { envConfig := box.Bytes(env + ".yml") viper.SetConfigType(configType) err = viper.ReadConfig(bytes.NewReader(envConfig)) if err != nil { return } } return }
調整之後,配置檔案也編譯至程式中,後續可以單執行檔案交付,只通過在啟動時指定GO_ENV
則可。