Привет, Хабр! Представляю вашему вниманию перевод статьи A no-nonsense guide to environment variables in Go автора Enda Phelan.
Переменные окружения — лучший способ хранения конфигурации приложения, поскольку они могут быть заданы на системном уровне. Это один из принципов методологии Twelve-Factor App, он позволяет отделять приложения от системы, в которой они запущены (конфигурация может существенно различаться между деплоями, код не должен различаться).
Всё, что необходимо для взаимодействия с переменными окружения есть в стандартной библиотеке os. Вот так можно получить значение переменной окружения PATH:
А так — установить значение переменной:
На девелоперской машине, где сразу запущено много проектов, хранить параметры в переменных окружениях не всегда удобно; логичнее будет разделить их между проектами с помощью env-файлов. Сделать это можно, например, с помощью godotenv — это портированная на Go Ruby-библиотека dotenv. Она позволяет устанавливать необходимые для приложения переменные окружения из файла .env.
Чтобы установить пакет запустим:
Добавим настройки в файл .env в корне проекта:
Теперь можно использовать эти значения в приложении:
Важно помнить, что если значение переменной окружения установлено на системном уровне, Go будет использовать именно это значение вместо указанного в env-файле.
Неплохо, конечно, иметь доступ к переменным окружения напрямую, как было показано выше, но вот поддерживать такое решение представляется довольно проблематичным. Имя переменной — это строка и, если оно изменится, то представьте себе головную боль, в которую выльется процесс обновления ссылок на переменную по всему приложению.
Чтобы решить эту проблему создадим конфигурационный модуль для работы с переменными окружения более централизованным и поддерживаемым способом.
Вот простой модуль config, который возвращает параметры конфигурации в структуре Config (также установим дефолтные значения параметров на случай, если переменной окружения в системе не окажется):
Далее добавим типы в структуру Config, поскольку имеющееся решение поддерживает только строковые типы, а это не очень-то разумно для больших приложений.
Создадим хэндлеры для типов bool, slice и integer:
Добавим в наш env-файл новые переменные окружения:
Теперь можно использовать их в любом месте приложения:
Да, существуют пакеты, предлагающие готовое решение для конфигурации вашего приложения, но насколько они необходимы, если это так легко сделать самостоятельно?
А как вы управляете конфигурацией в вашем приложении?
Переменные окружения — лучший способ хранения конфигурации приложения, поскольку они могут быть заданы на системном уровне. Это один из принципов методологии Twelve-Factor App, он позволяет отделять приложения от системы, в которой они запущены (конфигурация может существенно различаться между деплоями, код не должен различаться).
Использование переменных окружения
Всё, что необходимо для взаимодействия с переменными окружения есть в стандартной библиотеке os. Вот так можно получить значение переменной окружения PATH:
package main import ( "fmt" "os" ) func main() { // Store the PATH environment variable in a variable path, exists := os.LookupEnv("PATH") if exists { // Print the value of the environment variable fmt.Print(path) } }
А так — установить значение переменной:
package main import ( "fmt" "os" ) func main() { // Set the USERNAME environment variable to "MattDaemon" os.Setenv("USERNAME", "MattDaemon") // Get the USERNAME environment variable username := os.Getenv("USERNAME") // Prints out username environment variable fmt.Print(username) }
Загрузка переменных окружения из файла .env
На девелоперской машине, где сразу запущено много проектов, хранить параметры в переменных окружениях не всегда удобно; логичнее будет разделить их между проектами с помощью env-файлов. Сделать это можно, например, с помощью godotenv — это портированная на Go Ruby-библиотека dotenv. Она позволяет устанавливать необходимые для приложения переменные окружения из файла .env.
Чтобы установить пакет запустим:
go get github.com/joho/godotenv
Добавим настройки в файл .env в корне проекта:
GITHUB_USERNAME=craicoverflow GITHUB_API_KEY=TCtQrZizM1xeo1v92lsVfLOHDsF7TfT5lMvwSno
Теперь можно использовать эти значения в приложении:
package main import ( "log" "github.com/joho/godotenv" "fmt" "os" ) // init is invoked before main() func init() { // loads values from .env into the system if err := godotenv.Load(); err != nil { log.Print("No .env file found") } } func main() { // Get the GITHUB_USERNAME environment variable githubUsername, exists := os.LookupEnv("GITHUB_USERNAME") if exists { fmt.Println(githubUsername) } // Get the GITHUB_API_KEY environment variable githubAPIKey, exists := os.LookupEnv("GITHUB_API_KEY") if exists { fmt.Println(githubAPIKey) } }
Важно помнить, что если значение переменной окружения установлено на системном уровне, Go будет использовать именно это значение вместо указанного в env-файле.
Оборачиваем переменные окружения в конфигурационный модуль
Неплохо, конечно, иметь доступ к переменным окружения напрямую, как было показано выше, но вот поддерживать такое решение представляется довольно проблематичным. Имя переменной — это строка и, если оно изменится, то представьте себе головную боль, в которую выльется процесс обновления ссылок на переменную по всему приложению.
Чтобы решить эту проблему создадим конфигурационный модуль для работы с переменными окружения более централизованным и поддерживаемым способом.
Вот простой модуль config, который возвращает параметры конфигурации в структуре Config (также установим дефолтные значения параметров на случай, если переменной окружения в системе не окажется):
package config import ( "os" ) type GitHubConfig struct { Username string APIKey string } type Config struct { GitHub GitHubConfig } // New returns a new Config struct func New() *Config { return &Config{ GitHub: GitHubConfig{ Username: getEnv("GITHUB_USERNAME", ""), APIKey: getEnv("GITHUB_API_KEY", ""), }, } } // Simple helper function to read an environment or return a default value func getEnv(key string, defaultVal string) string { if value, exists := os.LookupEnv(key); exists { return value } return defaultVal }
Далее добавим типы в структуру Config, поскольку имеющееся решение поддерживает только строковые типы, а это не очень-то разумно для больших приложений.
Создадим хэндлеры для типов bool, slice и integer:
package config import ( "os" "strconv" "strings" ) type GitHubConfig struct { Username string APIKey string } type Config struct { GitHub GitHubConfig DebugMode bool UserRoles []string MaxUsers int } // New returns a new Config struct func New() *Config { return &Config{ GitHub: GitHubConfig{ Username: getEnv("GITHUB_USERNAME", ""), APIKey: getEnv("GITHUB_API_KEY", ""), }, DebugMode: getEnvAsBool("DEBUG_MODE", true), UserRoles: getEnvAsSlice("USER_ROLES", []string{"admin"}, ","), MaxUsers: getEnvAsInt("MAX_USERS", 1), } } // Simple helper function to read an environment or return a default value func getEnv(key string, defaultVal string) string { if value, exists := os.LookupEnv(key); exists { return value } return defaultVal } // Simple helper function to read an environment variable into integer or return a default value func getEnvAsInt(name string, defaultVal int) int { valueStr := getEnv(name, "") if value, err := strconv.Atoi(valueStr); err == nil { return value } return defaultVal } // Helper to read an environment variable into a bool or return default value func getEnvAsBool(name string, defaultVal bool) bool { valStr := getEnv(name, "") if val, err := strconv.ParseBool(valStr); err == nil { return val } return defaultVal } // Helper to read an environment variable into a string slice or return default value func getEnvAsSlice(name string, defaultVal []string, sep string) []string { valStr := getEnv(name, "") if valStr == "" { return defaultVal } val := strings.Split(valStr, sep) return val }
Добавим в наш env-файл новые переменные окружения:
GITHUB_USERNAME=craicoverflow GITHUB_API_KEY=TCtQrZizM1xeo1v92lsVfLOHDsF7TfT5lMvwSno MAX_USERS=10 USER_ROLES=admin,super_admin,guest DEBUG_MODE=false
Теперь можно использовать их в любом месте приложения:
package main import ( "fmt" "log" "github.com/craicoverflow/go-environment-variables-example/config" "github.com/joho/godotenv" ) // init is invoked before main() func init() { // loads values from .env into the system if err := godotenv.Load(); err != nil { log.Print("No .env file found") } } func main() { conf := config.New() // Print out environment variables fmt.Println(conf.GitHub.Username) fmt.Println(conf.GitHub.APIKey) fmt.Println(conf.DebugMode) fmt.Println(conf.MaxUsers) // Print out each role for _, role := range conf.UserRoles { fmt.Println(role) } }
Готово!
Да, существуют пакеты, предлагающие готовое решение для конфигурации вашего приложения, но насколько они необходимы, если это так легко сделать самостоятельно?
А как вы управляете конфигурацией в вашем приложении?
