Files
dev-conventions/arch/config.md
T
av 4a59c71737 заведён канон общих конвенций для личных проектов
- 13 конвенций по осям arch / lang / stack / common; репозитории берут
  оттуда копии в свой docs/conventions/ и коммитят их у себя
- conv — синхронизация копий: add / status / diff / pull / push, локальные
  регионы исключены из сравнения, поэтому расхождение не даёт шума
2026-07-25 18:18:18 +03:00

8.2 KiB
Raw Blame History

status
status
рекомендуемая

Конфигурация приложения

Как устроена конфигурация: где лежит, как попадает в процесс, что с секретами и когда падает.

Файл, а не окружение

Конфигурация — файл. Причины, по убыванию веса:

  • Один типизированный источник. Файл несёт секции, комментарии, единицы измерения и валидируется целиком. Окружение — плоский набор нетипизированных строк, который приходится документировать отдельно; появление второго канала конфигурации гарантирует расхождение между ними.
  • Окружение наследуется дочерними процессами. Всё, что приложение запускает — конвертер, git, шелл-хук, — по умолчанию получает копию секретов, хотя они ему не нужны.
  • В контейнере окружение расползается по лишним поверхностям. docker inspect показывает его любому, у кого есть доступ к сокету докера; переменные оседают в compose-файле и .env на диске — то есть файл всё равно появляется, только без структуры и валидации.

Обратите внимание, чего в списке нет: /proc/<pid>/environ не является аргументом — он имеет права 0400 и защищён проверкой PTRACE_MODE_READ, то есть доступен ровно тому же кругу, что и файл под 0600.

Запрет держится на «один источник» и на том, что все приложения свои. Для стороннего образа, живущего на env, конвенция неприменима — это не повод отказываться от неё для своих.

Практика:

  • Формат — текстовый, с комментариями и секциями (TOML, YAML — по стеку).
  • Имя по умолчанию фиксировано и ищется в рабочей директории процесса; путь переопределяется опцией командной строки.
  • Реальный конфиг не коммитится. В репозитории лежит образец.

Грузим один раз, дальше не перечитываем

  • Разбор — один раз при старте, в одну типизированную структуру. Дальше по коду читаем только её: чтения файла в бизнес-коде нет.
  • Конфиг неизменяем после старта; смена параметров — рестарт процесса. Горячая перезагрузка — отдельное решение с отдельным обоснованием, а не умолчание.
  • Умолчания задаются в коде, файл их перекрывает. Образец при этом перечисляет все поля, включая те, у которых есть умолчание: поле, живущее только в коде, для читателя конфига не существует.

Образец самодокументируем

Образец коммитим как единый справочник по конфигу: все секции и все поля. Каждое поле снабжаем комментарием, из которого ясно:

  • зачем поле — что оно меняет в поведении;
  • диапазон или допустимые значения — перечисление либо границы;
  • единицы измерения, если применимо: секунды/миллисекунды, байты, доля 01.

Так конфиг читается без открывания кода — этим он и полезен.

Поля по дискриминатору type

Когда набор полей секции зависит от поля-дискриминатора (выбор одного из бекендов или внешних сервисов), обязательность полей определяется его значением, а не фиксирована для секции.

  • Валидация — по значению type: для каждого поддерживаемого варианта свой набор обязательных полей; поля других вариантов не требуются. Неизвестное значение → ошибка на старте с перечислением поддерживаемых.
  • Образец — по значению type: основной вариант предзаполнен рабочими значениями, альтернативные — блоками-комментариями ниже, каждый со своим описанием полей. Из примера видны все варианты, не открывая код.

Секреты приносит деплой

Секреты доставляет деплой, рендеря их прямо в конфиг. Отдельного слоя секретов в приложении нет — оно просто читает файл. Источник истины секрета — внешнее хранилище деплоя, не репозиторий и не окружение.

  • Рендеренный конфиг не коммитится; права 0600, владелец — runtime- пользователь.
  • В образце секретные поля — пустые строки.
  • Загрузчик на старте проверяет, что обязательные секреты не пусты: это ловит криво отрендеренный шаблон до того, как он превратится в 401 от внешнего API через час работы.
  • В логи секреты не попадают.

Валидация и fail-fast

Конфиг валидируем на старте, до приёма трафика. Невалидный конфиг — запись уровня ERROR и выход с ненулевым кодом: не стартуем «наполовину».

Проверяем как минимум:

  • обязательные поля заданы, обязательные секреты не пусты;
  • пути существуют и доступны на запись/чтение по назначению;
  • числовые диапазоны и единицы (доли, таймауты, счётчики попыток);
  • строки, которые парсятся во что-то (длительности, зоны, URL), реально парсятся;
  • включённые секции консистентны: если интеграция включена — заданы все её обязательные поля.

Проблемы собираем и показываем разом, а не по одной за запуск.

Связано

  • arch/time.md — формат времени; зона отображения — единственный конфигурируемый параметр времени, семантика описана там.
  • arch/app-directories.md — конфиг лежит в категории «конфигурация» и доступен приложению только на чтение.