Viper для Go: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое 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                    

Рекомендаций