Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 15 из 25 ~16 мин +20 XP

RegisterAlias: синонимы ключей и миграция схемы

viper RegisterAlias создаёт синоним ключа: после регистрации алиасы ключей читают и пишут то же значение, что и основное имя, и переименование настройки перестаёт ломать чужие конфиги. Разберём направление аргументов, совместимость с BindEnv, дампы, в которых живут оба имени, и миграцию схемы без молчаливых дефолтов.

Содержание урока · 11 разделов

Релиз 2.0 ушёл в четверг, а в пятницу открылись тикеты: сервисы пользователей поднялись, но подключились не к своим базам, а к localhost. Причина оказалась скучной: мы переименовали ключ конфига из db_host в database.host, а старые config.yaml у клиентов остались с db_host. Viper честно прочитал файл целиком — значение под старым именем лежало в реестре, — но код спрашивал новое имя, не находил его и брал дефолт. Молча, без единой ошибки, сотни конфигов за один вечер. Средство от этого — viper RegisterAlias: вызов объявляет старое имя синонимом нового, и оба имени читают и пишут одно значение. В этом уроке разбираем механику алиасов, план миграции схемы конфига, связку с окружением и две детали, которые надо знать до первого переименования: порядок аргументов и невозможность снять алиас.

viper RegisterAlias: как устроен синоним#

RegisterAlias принимает два аргумента и складывает их в отдельную карту реестра: первый — алиас, второй — реальный ключ. После вызова viper.RegisterAlias("db_host", "database.host") имя db_host становится вторым именем database.host. Механика проявляется на каждом чтении: Get, GetString, GetInt и IsSet сперва разворачивают запрошенный ключ через карту алиасов и только потом спускаются по лестнице источников — Set, флаги, окружение, файл, дефолты. Поэтому через алиас видно ровно то же, что и через оригинал: если значение пришло из переменной окружения, алиас вернёт значение из окружения; если из файла — из файла. Запись тоже общая: Set через алиас попадает в реальный ключ, и значение сразу видно под обоими именами. Отсюда практический вывод: алиас — не копия значения, а синоним в реестре. Дубликата нет, синхронизировать нечего, а однонаправленность сопоставления означает лишь направление разворачивания — из синонима в ключ. Обратная ссылка не нужна: оба имени и так ведут в одно место.

Первый алиас: чтение и запись через оба имени

GO
viper.SetDefault("database.host", "localhost")
viper.RegisterAlias("db_host", "database.host")

fmt.Println(viper.GetString("db_host"))       // localhost — из реального ключа
fmt.Println(viper.GetString("database.host")) // localhost — то же значение

viper.Set("db_host", "db.internal:5432")      // запись через алиас
fmt.Println(viper.GetString("database.host")) // db.internal:5432

Значение живёт под реальным ключом database.host, а db_host работает как указатель на него. Чтение через алиас вернуло дефолт, запись через алиас легла в реальный ключ — и соседняя строка это показала. IsSet и GetInt ведут себя так же: разворачивание происходит до обращения к источникам.

Сценарии: короткие имена, совместимость, миграция#

Три сценария покрывают почти всё, зачем алиасы вообще нужны. Первый — миграция схемы: ключ переименован, старое имя остаётся рабочим синонимом, пока пользователи переносят свои конфиги; этому сценарию посвящён план ниже. Второй — совместимость с прошлой версией публичного инструмента: если ваш CLI держит у людей config.yaml, старые имена приходится поддерживать дольше, чем хотелось бы, и алиас дешевле ветвлений на каждом чтении. Третий — короткие имена: RegisterAlias("port", "server.port") позволяет писать в конфиг-файле port: 8080 вместо вложенной секции — удобно для примеров документации и быстрых правок руками. С короткими переменными окружения алиас не поможет: поиск по env идёт по развёрнутому ключу, и сокращение имени в реестре не меняет имя переменной — это работа для BindEnv с явным именем. Реестр имён из таблицы ниже стоит держать рядом со схемой конфига: без него через год никто не вспомнит, какое имя основное, а какое синоним.

Реестр имён: ключ → алиас → кто пользуется#

Реальный ключАлиасКто пользуется
database.hostdb_hostпользователи версии 1.x со старыми config.yaml
server.portportкороткие имена в примерах документации
log.levelloglevelстарые деплой-скрипты и манифесты
api.timeouttimeoutвнутренние команды поддержки

Миграция схемы конфига: план из трёх шагов#

Задача миграции: новое имя становится основным, старое остаётся синонимом, и никто не едет в дефолты. Шаг один — новое имя получает SetDefault, и весь ваш код переходит на database.host. Шаг два — до регистрации алиаса и до включения окружения значение переносится со старого имени: если чужой config.yaml содержит db_host, оно копируется на новое имя через Set. Без этого шага алиас бессилен: он разворачивает чтение алиаса в database.host, а значение из чужого файла лежит под db_host, куда код теперь не заглядывает — тот самый молчаливый дефолт из начала урока. Шаг три — RegisterAlias: с этого момента старое имя читается и пишется через новое, а данные, которые пользователи сами переложат на новое имя, живут обычной жизнью. Если старое значение могло приходить и из переменных окружения, перед переносом проверьте, что переменная пуста: Set стоит выше env на лестнице приоритетов и начнёт её глушить.

Миграция: старое имя живёт, переменная деплоя тоже

GO
viper.SetDefault("database.host", "localhost")
viper.ReadInConfig() // чужие config.yaml уже в реестре

// старое имя из файла переносим на новое (env ещё не включён)
if viper.IsSet("db_host") && os.Getenv("DB_HOST") == "" {
    viper.Set("database.host", viper.Get("db_host"))
}

viper.RegisterAlias("db_host", "database.host") // синоним навсегда
viper.BindEnv("database.host", "DB_HOST")       // старая переменная деплоя

fmt.Println(viper.GetString("db_host")) // файл, DB_HOST или localhost

Порядок строк критичен: перенос выполняется, пока db_host ещё не алиас и AutomaticEnv не включена, поэтому IsSet честно смотрит в файл. После RegisterAlias чтение через старое имя достаёт перенесённое значение, а BindEnv направляет на новый ключ старую переменную деплоя.

Границы алиаса: окружение, дампы, Unmarshal#

RegisterAlias живёт внутри реестра и не создаёт привязок к источникам. Привязка к окружению у ключа своя: SetEnvPrefix и BindEnv не узнают о синонимах. Поиск по переменным viper ведёт уже по развёрнутому имени: алиас db_host превращается в database.host, и AutomaticEnv проверяет DATABASE_HOST. Отсюда два следствия. Первое: старую переменную деплоя привязывайте к реальному ключу явно — BindEnv("database.host", "DB_HOST"). Второе: привязка, сделанная под именем алиаса, после регистрации синонима перестаёт работать — viper консультирует её только при чтении по развёрнутому имени. С дампами та же история вдобавок к приятному: алиасы попадают в AllKeys, и AllSettings покажет db_host рядом с database.host с одним значением. Unmarshal получит оба поля — mapstructure возьмёт то, что названо в тегах структуры, лишнее проигнорирует. А вот WriteConfig запишет оба имени в файл: при генерации конфигов для пользователей двойников придётся чистить.

Оба имени в дампе реестра

GO
viper.SetDefault("database.host", "localhost")
viper.RegisterAlias("db_host", "database.host")

fmt.Println(viper.AllKeys())
// [database.host db_host] — алиас перечислен рядом с реальным ключом

fmt.Println(viper.GetString("db_host"), viper.GetString("database.host"))
// localhost localhost — значение одно, имён два

AllKeys собирает имена из карты алиасов наравне с файлом и дефолтами, поэтому в дампе появляются двойники. Для логирования конфигурации это шум, для поиска забытых синонимов — подсказка. Проверьте локальным Go: два Println рядом с регистрацией алиаса дают полную картину за минуту.

Гигиена алиасов#

  • Первый аргумент RegisterAlias — синоним, второй — реальный ключ со значениями; перепутанный порядок слепит оба имени.
  • Алиас регистрируется рядом с SetDefault и до первых чтений: старое имя обязано работать с самого первого Get.
  • Значение из чужих старых конфигов переносится на новое имя до регистрации алиаса — сам по себе алиас данные не двигает.
  • В своём коде читайте и пишите только новое имя: алиас существует для внешних конфигов, а не для вас.
  • Алиас не создаёт привязок к окружению и флагам: BindEnv и BindPFlag настраиваются на реальный ключ отдельно.
  • AllKeys и AllSettings показывают оба имени, WriteConfig записывает двойников в файл — при генерации конфигов чистите лишнее.

Проверка: чтение через алиас

Выполнены viper.SetDefault("database.host", "localhost") и viper.RegisterAlias("db_host", "database.host"). Что вернёт viper.GetString("db_host")?

Проверка: снятие алиаса

Алиас db_host больше не нужен: все пользователи переехали на новое имя. Каким вызовом его снять?

Что выведет программа?

Прочитайте код и выберите, что он выведет:

viper.SetDefault("database.host", "localhost")
viper.RegisterAlias("db_host", "database.host")

viper.Set("db_host", "db.internal:5432")
fmt.Println(viper.GetString("database.host"))

Соберите миграцию ключа

Допишите три вызова: дефолт нового имени, регистрацию синонима и привязку старой переменной деплоя.

viper.("database.host", "localhost") viper.("db_host", "database.host") viper.("database.host", "DB_HOST")

Соберите секцию миграции

Расставьте строки: дефолт, алиас, привязка переменной и два чтения — до и после записи через старое имя.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: алиасы ключей

Термин

RegisterAlias

нажмите, чтобы перевернуть
Определение

viper.RegisterAlias(alias, key) — регистрирует синоним: первый аргумент — новое имя, второй — существующий ключ. Чтение, запись и IsSet работают через оба имени.

1 / 6

Спор: алиасы навсегда или config_version#

Моё отношение к алиасам двойственное, и на code review я его формулирую так: алиасы — отличное средство разовой миграции и плохой способ постоянного дизайна. Как склейка после переименования они идеальны: пара строк кода, ноль сломанных пользователей. Но без присмотра синонимы превращаются в свалку: имена плодятся с каждым релизом, дампы двоятся, и через два года команда не помнит, какое из четырёх имён ключа основное. Альтернатива для проектов, где схема конфига меняется регулярно, — версионирование: ключ config_version в начале файла и явный мигратор, который при чтении поднимает конфиг с первой версии до текущей, и каждый шаг перехода виден в истории кода. Плата — инфраструктура: список версий, переходники, тесты на миграции. Критерий простой: один-два переименованных ключа — алиасы и строка в changelog; схема движется каждые пару релизов или конфиги генерируют внешние системы — config_version с мигратором. Правы бывают оба подхода; ошибка — не выбрать ни один и копить синонимы молча.

Что дальше#

Алиасы закрыли переименования: схема конфига двигается, а старые имена продолжают работать. Следующий урок — про профили окружений: один бинарник в dev, staging и prod, файлы config.dev.yaml и config.prod.yaml, выбор контура через флаг и APP_ENV — и правило, по которому секреты в файлах не живут вовсе. Полный маршрут по темам — на странице курса viper. Потрогайте миграцию локальным Go: соберите секцию из урока, положите рядом config со старым именем и проверьте, что чтение через оба имени возвращает одно значение до и после Set через алиас.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • RegisterAlias(alias, key) объявляет синоним: первый аргумент — новое имя, второй — реальный ключ; чтение, запись и IsSet работают через оба имени.
  • Алиас разворачивается в реальный ключ до поиска по источникам, поэтому через оба имени видны одни и те же значения из Set, env, файла и дефолтов.
  • Значение из чужих старых конфигов переносится на новое имя до регистрации алиаса: сам по себе алиас данные не двигает.
  • Алиас не создаёт привязку к окружению: старую переменную деплоя привязывайте к реальному ключу явно — BindEnv("database.host", "DB_HOST").
  • AllKeys и AllSettings показывают оба имени, а WriteConfig записывает двойников в файл — при генерации конфигов их чистят.
  • Снять алиас нельзя: публичного UnregisterAlias нет, синоним живёт до конца процесса; в тестах — отдельный реестр viper.New().
  • Для пары переименованных ключей алиасы дешевле всего; при регулярной смене схемы вводите config_version и явный мигратор.

Домашнее задание

Возьмите конфиг с ключом server.port: переименуйте его в http.port, добавьте RegisterAlias для старого имени и BindEnv для HTTP_PORT. Проверьте чтение через оба имени, запись через алиас и напечатайте AllKeys — убедитесь, что двойники видны. Затем попробуйте снять алиас и посмотрите, какие варианты вам доступны.

Личные заметкиПусто
Хранятся только в вашем браузере (Viper · RegisterAlias: синонимы ключей и миграция схемы)

Вопросы и ответы по уроку

Чем алиас отличается от Set с тем же значением?

Set копирует значение в реестр в момент вызова: дальше две копии живут своей жизнью, и после правки одной вторая устаревает. Алиас копии не создаёт — оба имени читают один и тот же источник, синхронизировать нечего.

Работает ли алиас для вложенных ключей?

Да: RegisterAlias("dbh", "database.host") даёт короткое имя вложенному ключу, разворачивание происходит на каждом чтении. Пригодится и для Unmarshal — только помните, что в итоговую карту попадут оба поля, а не одно.

Можно ли зарегистрировать несколько алиасов на один ключ?

Да: каждый вызов RegisterAlias добавляет ещё одно имя, и все они разворачиваются в один реальный ключ. Проблема не в количестве, а в забвении: держите реестр имён из этого урока рядом со схемой конфига и объявляйте срок жизни синонимов в changelog.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Chi25 уроков~9 чпрактика25 уроков уже доступны

Chi: роутер для Go HTTP-сервисов

Интерактивный курс Chi — роутера Go на net/http: маршруты, middleware, группы, контекст, JSON API, таймауты, graceful shutdown. 25 уроков с примерами и практикой.

  • Строить маршруты chi: методы, параметры пути, wildcards
  • Писать middleware и понимать порядок их выполнения
  • Группировать маршруты: Route, Group и подроутеры
  • Прокидывать данные в хендлеры через context.Context