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

123 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 через час работы.
- В логи секреты не попадают.
<!-- local:секретные-поля -->
<!-- /local -->
## Валидация и fail-fast
Конфиг валидируем **на старте, до приёма трафика**. Невалидный конфиг —
запись уровня `ERROR` и выход с ненулевым кодом: не стартуем «наполовину».
Проверяем как минимум:
- обязательные поля заданы, обязательные секреты не пусты;
- пути существуют и доступны на запись/чтение по назначению;
- числовые диапазоны и единицы (доли, таймауты, счётчики попыток);
- строки, которые парсятся во что-то (длительности, зоны, URL), реально
парсятся;
- включённые секции консистентны: если интеграция включена — заданы все её
обязательные поля.
Проблемы собираем и показываем **разом**, а не по одной за запуск.
<!-- local:проверки -->
<!-- /local -->
## Связано
- `arch/time.md` — формат времени; зона отображения — единственный
конфигурируемый параметр времени, семантика описана там.
- `arch/app-directories.md` — конфиг лежит в категории «конфигурация» и
доступен приложению только на чтение.
<!-- local:связано -->
<!-- /local -->