Перейти к основному содержимому

Источники конфига

ConfigSource — абстракция над местом, откуда загружается конфигурация. В Phase 1 главный источник — YamlFileSource (файлы на диске), но API рассчитан на расширение. Phase 2+ предусматривает адаптеры для централизованных хранилищ конфигов:

ИсточникПримерСтатус
YamlFileSourceapp-config.yaml на дискеPhase 1 (default)
JsonFileSourceapp-config.json на дискеPhase 1
InMemorySource (Python/TS) / DictSource (Go)Dict, собранный программно — для тестовPhase 1
env-интерполяция ${VAR}Чтение окружения на этапе обработки raw-текстаPhase 1 (встроен, не отдельный source)
EtcdSourceКлючи в etcd под префиксомPhase 2 (план)
ConsulSourceKV store в ConsulPhase 2 (план)
VaultSourceСекреты в HashiCorp VaultPhase 2 (план)
HttpSourceREST endpoint с JSON-ответомPhase 2 (план)
SqlSourceТаблица в БД с конфигом, меняющимся в рантаймеPhase 2 (план)
KubernetesSourceConfigMap + Secret в KubernetesPhase 2 (план)

Интерфейс

Любой source реализует минимальный контракт:

id: string # уникальный идентификатор источника (например, "yaml:/app/config.yaml")
load(): ConfigTree # одноразовая загрузка (обязательно)
watch?(callback): Subscription # подписка на изменения (опционально)
close?(): void # освобождение ресурсов (опционально)

load() возвращает ConfigTree — дерево значений, которое загрузчик затем мерджит с другими источниками и применяет env-интерполяцию.

watch(callback) — опциональный. Если источник поддерживает watch (файл: через fsnotify; etcd: через gRPC-stream; Kubernetes: через informer), то загрузчик передаёт callback для уведомления об изменениях. Если источник не поддерживает — метод не реализован, подписки на изменения регистрируются, но активируются с active = false.

Базовый сценарий — YamlFileSource

Для большинства dagstack-приложений Config.load(path) — высокоуровневая обёртка над Config.loadFrom([YamlFileSource(path), ...]). Она автоматически обнаруживает и подключает три слоя:

  1. app-config.yaml — base (коммитится).
  2. app-config.local.yaml — переопределения разработчика (gitignored).
  3. app-config.${DAGSTACK_ENV}.yaml — специфичные для окружения (коммитятся).

Детали слоирования — на странице Слои.

Явное перечисление источников

Когда нужен нестандартный порядок (например, в тестах) или смешение типов источников, используйте Config.loadFrom:

from dagstack.config import Config, YamlFileSource, InMemorySource

config = Config.load_from([
YamlFileSource("app-config.yaml"),
InMemorySource({"database": {"pool_size": 5}}), # test-override
])

:::note Имя in-memory источника В Python и TypeScript источник называется InMemorySource, в Go — DictSource. Это историческое расхождение между биндингами; семантика идентична (in-memory tree, без интерполяции по умолчанию). :::

Порядок аргументов = порядок приоритета. Первый источник — наименьший приоритет, последний — наибольший (переопределяет предыдущие).

Подстановка env-переменных

Env-переменные — не отдельный ConfigSource, а этап обработки значений в загруженном ConfigTree. Строковые значения вида ${VAR} или ${VAR:-default} интерполируются сразу после source.load(), до merge с другими источниками.

Детали — на странице Подстановка env-переменных.

Watch-семантика и hot-reload

Когда приложение подписывается на изменения своей секции через config.onSectionChange(path, callback), загрузчик регистрирует наблюдателя на каждом источнике, поддерживающем watch:

  1. source.watch(on_source_change) регистрируется во всех источниках.
  2. Любое изменение в источнике → загрузчик заново собирает дерево.
  3. Новая версия валидируется (если подписанные секции имеют schema).
  4. Если валидация прошла — подписчики получают новые значения.
  5. Если валидация не прошла — вся операция перезагрузки откатывается; подписчики не уведомляются (атомарный откат).

Если ни один источник не поддерживает watch — все подписки регистрируются с active = false. Это — не ошибка: приложение может успешно запускаться без hot-reload.

Подробнее — на странице Hot-reload (watch).

Свой собственный ConfigSource

v1.0 контракт фиксирует сигнатуры, но не формат на диске. Если вам нужен кастомный источник (например, ZooKeeper), реализуйте интерфейс и передайте экземпляр в Config.loadFrom:

from dagstack.config import ConfigSource, ConfigTree, Subscription

class ZookeeperSource(ConfigSource):
id = "zookeeper://zk.example.com/my-app"

def __init__(self, host: str, prefix: str):
self._client = zk_connect(host)
self._prefix = prefix

def load(self) -> ConfigTree:
raw = self._client.get_recursive(self._prefix)
return ConfigTree.from_dict(raw)

def watch(self, callback) -> Subscription:
# callback(event) когда дерево меняется
...

def close(self) -> None:
self._client.close()

См. также