Что такое Viper и зачем он нужен?
Viper — это одна из самых популярных библиотек для управления конфигурацией в приложениях на Go. Она предоставляет универсальный и гибкий способ работы с настройками, которые могут поступать из различных источников: файлы конфигурации (JSON, TOML, YAML, HCL, INI, envfile), переменные окружения, флаги командной строки, удалённые хранилища (etcd, Consul) и даже значения по умолчанию, заданные в коде.
Зачем нужен Viper? В современной разработке конфигурация редко бывает статичной. Приложение может запускаться в разных окружениях (dev, staging, production), использовать разные базы данных, API-ключи и порты. Viper решает несколько ключевых задач:
- Иерархическая структура: позволяет вкладывать настройки друг в друга (например,
database.host). - Приоритетность источников: чётко определяет, какой источник имеет больший приоритет (например, флаги переопределяют переменные окружения).
- Автоматическое связывание: может автоматически связывать переменные окружения с ключами конфигурации.
- Поддержка множества форматов: не нужно писать парсеры для каждого формата.
- Безопасность типов: предоставляет методы для получения значений с приведением к нужному типу (
GetInt,GetString,GetBoolи т.д.).
Viper идеально подходит для создания микросервисов, веб-приложений, CLI-утилит и любых других программ на Go, где требуется гибкое управление настройками.
Установка Viper
Viper устанавливается стандартным для Go способом — через менеджер пакетов go get. Убедитесь, что у вас установлен Go версии 1.18 или выше.
go get github.com/spf13/viper
После выполнения этой команды Viper будет добавлен в ваш файл go.mod. Для работы с конкретными форматами файлов (например, YAML) вам также могут понадобиться дополнительные библиотеки, но Viper поставляется с базовой поддержкой JSON, TOML и YAML «из коробки». Если вы используете go mod, зависимости подтянутся автоматически.
Пример импорта в коде:
import (
"github.com/spf13/viper"
)
Быстрый старт: первый пример с Viper
Давайте напишем простую программу, которая читает конфигурацию из файла config.yaml и выводит приветствие.
Создайте файл config.yaml:
app:
name: "MyApp"
version: "1.0.0"
server:
port: 8080
Теперь создайте файл main.go:
package main
import (
"fmt"
"log"
"github.com/spf13/viper"
)
func main() {
// Устанавливаем имя файла конфигурации
viper.SetConfigName("config")
// Устанавливаем тип файла
viper.SetConfigType("yaml")
// Добавляем пути поиска
viper.AddConfigPath(".")
// Читаем конфигурацию
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Ошибка чтения конфигурации: %s", err)
}
// Получаем значения
appName := viper.GetString("app.name")
appVersion := viper.GetString("app.version")
port := viper.GetInt("server.port")
fmt.Printf("Приложение: %s (версия %s) запущено на порту %d\n", appName, appVersion, port)
}
Запустите программу:
go run main.go
Вывод:
Приложение: MyApp (версия 1.0.0) запущено на порту 8080
Этот простой пример демонстрирует основную механику: мы указываем Viper, где искать файл, читаем его, а затем извлекаем значения по ключам.
Основные методы и функции Viper
Viper предоставляет богатый API. Ниже мы разберём 10 ключевых методов с подробным описанием и примерами.
1. SetConfigName
Что делает: Устанавливает имя файла конфигурации (без расширения). Viper будет искать файлы с этим именем и различными расширениями (yaml, json, toml и т.д.).
Параметры: name string — имя файла.
Пример:
viper.SetConfigName("app") // Будет искать app.yaml, app.json, app.toml и т.д.
2. SetConfigType
Что делает: Явно задаёт тип файла конфигурации. Это полезно, если у файла нет расширения или вы хотите принудительно использовать определённый формат.
Параметры: typ string — тип (например, "yaml", "json", "toml").
Пример:
viper.SetConfigType("yaml")
3. AddConfigPath
Что делает: Добавляет путь для поиска файлов конфигурации. Можно вызывать несколько раз, чтобы добавить несколько директорий.
Параметры: path string — путь к директории.
Пример:
viper.AddConfigPath(".") // Текущая директория
viper.AddConfigPath("/etc/myapp/") // Системная директория
4. ReadInConfig
Что делает: Читает и парсит найденный файл конфигурации. Возвращает ошибку, если файл не найден или не может быть прочитан.
Параметры: Нет.
Пример:
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Ошибка: %s", err)
}
5. GetString
Что делает: Возвращает значение по ключу в виде строки. Если ключ не найден, возвращает пустую строку.
Параметры: key string — ключ (может быть вложенным, например "database.host").
Пример:
host := viper.GetString("database.host")
fmt.Println(host) // Вывод: localhost (если такой ключ есть)
6. GetInt
Что делает: Возвращает значение по ключу в виде целого числа (int). Если значение не может быть приведено к int, возвращает 0.
Параметры: key string — ключ.
Пример:
port := viper.GetInt("server.port")
fmt.Println(port) // Вывод: 8080
7. GetBool
Что делает: Возвращает значение по ключу в виде булевого значения. Понимает строки "true", "false", "1", "0" и т.д.
Параметры: key string — ключ.
Пример:
debug := viper.GetBool("debug")
if debug {
fmt.Println("Режим отладки включён")
}
8. SetDefault
Что делает: Устанавливает значение по умолчанию для указанного ключа. Это значение будет использовано, если ключ не найден ни в одном из источников.
Параметры: key string, value interface{}.
Пример:
viper.SetDefault("server.port", 3000)
viper.SetDefault("app.name", "DefaultApp")
port := viper.GetInt("server.port") // Вернёт 3000, если не задано иное
fmt.Println(port)
9. BindEnv
Что делает: Связывает переменную окружения с ключом конфигурации. Это позволяет переопределять настройки через переменные окружения.
Параметры: input ...string — один или два аргумента. Если один, то имя переменной окружения автоматически преобразуется в ключ (например, "MYAPP_PORT" -> "myapp.port"). Если два, то первый — имя переменной окружения, второй — ключ.
Пример:
// Связываем переменную MYAPP_PORT с ключом server.port
viper.BindEnv("MYAPP_PORT", "server.port")
// Теперь, если установлена переменная MYAPP_PORT=9090, то viper.GetInt("server.port") вернёт 9090
10. AllSettings
Что делает: Возвращает карту (map[string]interface{}) всех настроек, которые были прочитаны или установлены. Полезно для отладки или передачи конфигурации в другой модуль.
Параметры: Нет.
Пример:
settings := viper.AllSettings()
for key, value := range settings {
fmt.Printf("%s: %v\n", key, value)
}
11. Unmarshal
Что делает: Преобразует всю конфигурацию или её часть в структуру Go. Это самый удобный способ работы с настройками, так как вы получаете типизированную структуру.
Параметры: rawVal interface{} — указатель на структуру, в которую нужно разобрать конфигурацию.
Пример:
type Config struct {
App struct {
Name string `mapstructure:"name"`
Version string `mapstructure:"version"`
} `mapstructure:"app"`
Server struct {
Port int `mapstructure:"port"`
} `mapstructure:"server"`
}
var config Config
if err := viper.Unmarshal(&config); err != nil {
log.Fatalf("Ошибка разбора: %s", err)
}
fmt.Println(config.App.Name)
12. WatchConfig
Что делает: Запускает горутину, которая отслеживает изменения в файле конфигурации. При изменении файла можно выполнить callback-функцию. Требует установки пакета github.com/fsnotify/fsnotify.
Параметры: Нет (но можно использовать OnConfigChange для задания callback).
Пример:
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Конфигурация изменена:", e.Name)
})
Реальный практический пример: веб-сервер с конфигурацией
Давайте создадим полноценный веб-сервер, который использует Viper для загрузки настроек из нескольких источников: файл, переменные окружения и значения по умолчанию.
Структура проекта:
myapp/
├── config.yaml
├── main.go
└── go.mod
config.yaml:
server:
host: "0.0.0.0"
port: 8080
database:
host: "localhost"
port: 5432
user: "postgres"
password: "secret"
dbname: "mydb"
logging:
level: "info"
format: "json"
main.go:
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/spf13/viper"
)
type Config struct {
Server struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
} `mapstructure:"server"`
Database struct {
Host string `mapstructure