iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0

Why file and environment variables ?

  • 當開發和部署後端網頁應用時,我們通常需要為不同的環境使用不同的配置,例如開發、測試、暫存和生產環境。
  • 從文件中讀取值可以讓我們輕鬆地為本地開發和測試指定預設配置。
  • 而從環境變數中讀取值將幫助我們使用Docker容器部署應用程序到暫存或生產環境時,覆蓋預設設置。

https://ithelp.ithome.com.tw/upload/images/20231006/20121746sYgractr9D.png

Why Viper

https://ithelp.ithome.com.tw/upload/images/20231006/20121746hheZCwUtFv.png

  1. 它可以尋找、加載和解析配置文件中的值。
  2. 它支持多種文件類型,例如 JSON、TOML、YAML、ENV 或 INI。
  3. 它還可以從環境變數或命令行標誌中讀取值。
  4. 它賦予我們設置或覆蓋預設值的能力。
  5. 此外,如果你更喜歡將設置存儲在如 Etcd 或 Consul 之類的遠程系統中,那麼你可以使用 viper 直接從它們那裡讀取數據。
  6. 它適用於未加密和加密的值。
  7. 關於 Viper 的另一個有趣之處是,它可以監視配置文件中的更改,並通知應用程序。
  8. 我們還可以使用 viper 保存我們對文件所做的任何配置修改。

Load Config in Go with viper

What will we do

在我們的簡易銀行專案的當前代碼中,我們在**main_test.go文件中硬編碼了dbDriverdbSource等常數,並在main.go文件中為serverAddress hard code的**常數。

db/sqlc/main_test.go

const (
	dbDriver = "postgres"
	dbSource = "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable"
)
main.go

const (
	dbDriver      = "postgres"
	dbSource      = "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable"
	serverAddress = "0.0.0.0:8080"
)

Install Viper

  • 安裝之後,在我們的專案的**go.mod**文件中,我們可以看到Viper已被添加為依賴項。
go get github.com/spf13/viper

Create config file

  • 我將創建一個新文件**app.env來存儲我們的開發配置值。接著,我們從main.go**文件中複製這些變數並將它們copy到此配置文件中。

  • 我們使用的是dot env format,因此必須更改聲明這些變數的方式。

    • 每個變數應該在單獨的一行上宣告。
    • 變數的名稱應該為大寫,且其單詞之間由底線分隔。
    • 變數的值應該在等號後跟隨其名稱。
    app.env
    
    DB_DRIVER=postgres
    DB_SOURCE=postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable
    SERVER_ADDRESS=0.0.0.0:8080
    

Load config file

  • 在**util包中創建一個新文件config.go**。

  • 在此文件中聲明一個新類型的**Config結構。這個Config**結構將保存從文件或環境變數中讀取的應用程序的所有配置變數。

  • 我們必須使用與在**app.env中宣告的每個變數的確切名稱**。例如,DBDriver的標籤名稱應該是DB_DRIVERDBSource的標籤名稱應該是DB_SOURCE,同樣地,ServerAddress的標籤名稱應該是SERVER_ADDRESS

  • Viper 在底層使用**mapstructure包進行unmarshaling,因此我們使用mapstructure**標籤來指定每個配置字段的名稱。

    util/config.go
    
    type Config struct {
        DBDriver      string `mapstructure:"DB_DRIVER"`
        DBSource      string `mapstructure:"DB_SOURCE"`
        ServerAddress string `mapstructure:"SERVER_ADDRESS"`
    }
    
  • 接著,定義一個新函數**LoadConfig()**,它接受一個path作為輸入,並返回一個配置對象或一個錯誤。

  • 這個函數將從path中的配置文件中讀取配置,或者如果提供了環境變數,則覆蓋它們的值。

  • 首先,我們呼叫viper.AddConfigPath()以告知Viper配置文件的位置。在此情況下,位置由輸入路徑參數給定。

  • 接著,我們呼叫viper.SetConfigName()來告訴Viper尋找具有特定名稱的配置文件。我們的配置文件是app.env,所以它的名稱是app

  • 我們還通過呼叫viper.SetConfigFile()並傳入env來告知Viper配置文件的類型。在這種情況下是env。如果需要,也可以使用JSON、XML或其他任何格式,只需確保您的配置文件格式和擴展名正確。

  • 除了從文件讀取配置外,我們還希望viper從環境變數讀取值。因此,我們呼叫viper.AutomaticEnv()告知viper自動使用對應的環境變數值覆蓋它從配置文件讀取的值(如果存在的話)。

  • 之後,我們呼叫viper.ReadInConfig()開始讀取配置值。如果錯誤不為nil,則我們簡單地返回它。

  • 否則,我們呼叫viper.Unmarshal()將值解封裝到目標配置對象中。最後,僅返回配置對象及其任何發生的錯誤。

  • 總的來說,加載配置功能已完成。現在,我們可以在main.go文件中使用它。

  • err 是作為函數的返回值之一被預先定義的。當您在函數簽名中定義了返回值的名稱(在這裡是 configerr),那麼在函數體內部,這些變數已經被初始化並可以直接使用。

    // LoadConfig從文件或環境變數讀取配置。 
    func LoadConfig(path string) (config Config, err error) {
        viper.AddConfigPath(path)
        viper.SetConfigName("app")
        viper.SetConfigType("env")
    
        viper.AutomaticEnv()
    
        err = viper.ReadInConfig()
        if err != nil {
            return
        }
    
        err = viper.Unmarshal(&config)
        return
    }
    
    

Use LoadConfig in the main function

  • 去除main.go所有的Hard Code
  • main()函數中,呼叫util.LoadConfig(),並在此處傳入".",這表示當前文件夾,因為我們的配置文件app.env與這個main.go文件位於相同的位置。
  • 如果出現錯誤,則寫入一條說明"cannot load config"的log。否則,我們只需將這些變量更改為config.DBDriverconfig.DBSourceconfig.ServerAddress
main.go

func main() {
    config, err := util.LoadConfig(".")
    if err != nil {
        log.Fatal("cannot load config:", err)
    }

    conn, err := sql.Open(config.DBDriver, config.DBSource)
    if err != nil {
        log.Fatal("cannot connect to db:", err)
    }

    store := db.NewStore(conn)
    server := api.NewServer(store)

    err = server.Start(config.ServerAddress)
    if err != nil {
        log.Fatal("cannot start server:", err)
    }
}

  • 運行伺服器: 執行以下命令:
  • 結果顯示: 伺服器正常運作並監聽在localhost8080端口,這與我們在app.env文件中指定的一致。
  • 總結:伺服器順利運作,並正確地根據app.env文件中的設定監聽在localhost8080端口。
make server
go run main.go

Use LoadConfig in the test

func TestMain(m *testing.M) {
	var err error
	config, err := util.LoadConfig("../..")
	if err != nil {
		log.Fatal("cannot load config:", err)
	}

	
	testDB, err = sql.Open(config.DBDriver, config.DBSource)
	if err != nil {
			log.Fatal("cannot connect to db: ", err)
	}

	testQueries = New(testDB)

	exitCode := m.Run()

	os.Exit(exitCode)
}

上一篇
[Day 20] Implement RESTful in GO using Gin Part 3
下一篇
[Day 22] Mock DB for testing HTTP API in Go and achieve 100% coverage Part 1
系列文
Techschool Goalng Backend Master Class 的學習記錄31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言