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

Объявление секции конфигурации

Каждый компонент / плагин / сервис в приложении должен объявить свою секцию конфигурации — ключ верхнего уровня в YAML и схему, которая ему соответствует.

Шаг 1. Выбери имя секции

Соглашения по именованию:

РекомендацияПример
snake_casedatabase, payment_provider, rate_limit
Существительное в единственном числеdatabase, не databases
Без суффиксов _config / _settingsdatabase, не database_config
Коротко, но уникально2–3 слова через _

Для плагинов имя секции нормативно: <kind>.<name> (ADR-0001 §4).

Шаг 2. Опиши схему

app/database.py
from pydantic import BaseModel, Field, field_validator


class DatabaseConfig(BaseModel):
host: str
port: int = Field(5432, ge=1, le=65535)
name: str
user: str
password: str = Field(..., min_length=1)
pool_size: int = Field(20, ge=1, le=1000)
ssl: bool = False

@field_validator("host")
@classmethod
def must_not_be_wildcard(cls, v: str) -> str:
if v in ("0.0.0.0", "*"):
raise ValueError("host must be a concrete address")
return v

Требования к схеме:

  • Каждое обязательное поле явно помечено (Field(...) без default в pydantic; z.string() без .default() в zod; validate:"required" в struct-теге Go).
  • Значения по умолчанию прописаны для каждого опционального поля.
  • Валидация диапазонов (min / max / pattern) — декларативно, чтобы не прописывать её в рантайме.
  • Типизированные поля — избегай Any / unknown; у каждого поля явный тип.

Шаг 3. Прочитай секцию

Имя метода различается по биндингам (get_section / getSection / GetSection):

from dagstack.config import Config

config = Config.load("app-config.yaml")
db_cfg = config.get_section("database", DatabaseConfig)
# db_cfg — экземпляр DatabaseConfig, уже провалидированный.

Метод:

  1. Извлекает поддерево по заданному пути.
  2. Запускает валидацию по схеме.
  3. Возвращает типизированный объект.

При сбое → ConfigError(validation_failed) с указанием поля, ожидаемого типа и фактического значения (с маскированными секретными полями).

Шаг 4. Изоляция — читай только свою секцию

Правило: компонент читает только свою секцию. Чтение чужой секции — антипаттерн.

# Правильно — внутри сервиса базы данных:
db_cfg = config.get_section("database", DatabaseConfig)

# Неправильно — сервис БД читает чужую секцию:
cache_cfg = config.get_section("cache", CacheConfig)
# Сервис БД теперь зависит от структуры cache.

Почему:

  • Связанность. Если сервис A читает конфиг сервиса B, изменение схемы B ломает A. Скрытая зависимость без явных границ.
  • Тестируемость. Замокать только секцию database проще, чем мокать весь конфиг с секциями всех компонентов.
  • Безопасность. В production у сервисов могут быть разные права на чтение разных секций (через governance-middleware); жёстко зашитое чтение чужой секции обходит эту политику.

Правильная альтернатива: если двум сервисам нужен общий параметр (например, общий max_payload_mb), вынеси его в отдельную секцию shared и читай из обеих:

shared:
max_payload_mb: 10

database:
host: "localhost"
pool_size: 20

api:
host: "0.0.0.0"
port: 8080

Шаг 5. Значения по умолчанию для опциональных полей

Все опциональные поля объявляют свои значения по умолчанию в схеме, не в коде:

class DatabaseConfig(BaseModel):
host: str # обязательное
user: str # обязательное
password: str # обязательное
port: int = 5432 # default в схеме
pool_size: int = 20 # default в схеме
ssl: bool = False # default в схеме

Антипаттерн — навязывание значений по умолчанию в коде после чтения:

# НЕПРАВИЛЬНО — defaults в коде:
cfg = config.get_section("database", DatabaseConfig)
port = cfg.port or 5432 # ← такой default делает схему нечестной
pool = cfg.pool_size or 20

Если default задан в схеме, get_section / getSection / GetSection всегда возвращает полностью заполненный объект. Коду не приходится угадывать.

Шаг 6. Документируй секцию

Каждая секция должна быть задокументирована:

  • Описание для каждого поля — комментарий / docstring в схеме.
  • Пример конфига — фрагмент app-config.yaml в README компонента.
  • Заметки про диапазоны — почему pool_size ограничен интервалом 1–1000, а не безграничен сверху.
class DatabaseConfig(BaseModel):
"""Configuration for application database connection.

Note: password is masked in diagnostic output automatically.
"""

host: str = Field(
...,
description="Database server host.",
examples=["localhost", "db.internal.example.com"],
)
port: int = Field(
5432,
description="TCP port of the database server.",
ge=1,
le=65535,
)
pool_size: int = Field(
20,
description="Connection pool size. Range: 1-1000. Typical dev: 5-10, prod: 50-200.",
ge=1,
le=1000,
)

description / examples в pydantic.Field попадают в JSON-схему, которая дальше всплывает в подсказках IDE и в авто-генерируемой документации.

См. также