Архитектура feature flags
Типы флагов (release/experiment/ops/permission), хранение, оценка, тех-долг и удаление.
Спроектируй систему feature flags для контекста: {{context}}.
Принцип: флаг — это код. У него есть владелец, срок жизни и план удаления. Без этого вы получаете 2000 флагов через 2 года.
1. Типы флагов (важно различать)
Каждый тип требует своего хранения, инвалидации и lifecycle:
| Тип | Назначение | Lifetime | Аудитория |
|---|---|---|---|
| Release | Спрятать незавершённую фичу | дни-недели | весь трафик |
| Experiment | A/B/N тест | недели | сегмент пользователей |
| Ops / kill switch | Аварийно отключить дорогую/опасную фичу | вечно | весь трафик, мгновенно |
| Permission / entitlement | Платная/гейт-фича | вечно | по плану/роли |
Не смешивай. Permission-флаг с TTL 1 минута загнётся под нагрузкой; ops-флаг с медленной инвалидацией убьёт сервис.
2. Lifecycle release-флага
created → off in prod → enabled for internal → 1% → 10% → 50% → 100% → cleanup
↓
delete flag + dead branch
Правила:
- При создании — owner, expected lifetime, jira/ticket
- Reminder через 30 дней после 100%: пора чистить
- CI блокирует merge нового флага без полей выше
- Раз в квартал: список флагов старше N месяцев → owner отчитывается
3. Хранение и оценка
Локально (в коде/конфиге)
Простейший вариант: config.ts с константами. Деплой = смена флага.
- Плюс: zero infrastructure, типы, atomic с кодом
- Минус: для смены — деплой; нет per-user; нет ops-switch без релиза
- Когда: маленький проект, release-only флаги
Через переменные окружения / конфиг
process.env.FEATURE_X. Меняется через перезапуск.
- Когда: ops-switch'и с реstart'ом приемлемы
Через сервис флагов
LaunchDarkly, Unleash, Statsig, ConfigCat — или своё на Redis/Postgres.
- Real-time updates, per-user/segment, rollout %, audit log
- Минус: extra dependency, latency, надо защищать от падения провайдера
- Когда: experiments, permission, ops
Hybrid
Release-флаги в коде, ops/permission в сервисе. Каждому типу — свой механизм.
4. Контракт оценки (evaluation context)
Флаг оценивается детерминированно для одного и того же контекста. Контекст =
- user id (для experiments — стабильное хэширование!)
- tenant
- environment (dev / staging / prod)
- request attrs (страна, plan, beta-cohort)
const ctx = { userId, tenantId, plan, country, env };
if (flags.isOn('new_checkout', ctx)) { ... }
Sticky bucketing: user либо в эксперименте, либо нет — не должен переключаться между визитами. Хэшируй hash(userId + flagKey) % 100 < rollout%.
5. Производительность
- Получай флаги батчем в начале запроса, не по одному
- Кешируй decisions per request (одна и та же оценка много раз)
- Локальный SDK с polling/streaming, не fetch на каждый check
- Fallback значение в коде на случай недоступности провайдера
const value = flags.boolean('new_checkout', ctx, { fallback: false });
6. Реализация в коде
Чистый guard
if (await flags.isOn('new_checkout', ctx)) {
return newCheckoutFlow();
}
return oldCheckoutFlow();
Без if-внутри-функции (Strategy)
const checkout = flags.isOn('new_checkout', ctx) ? newCheckout : oldCheckout;
return checkout(payload);
Pattern: scientist / dark launch
const old = await oldImpl();
if (flags.isOn('compare_new', ctx)) {
void runShadowAndCompare(newImpl, old); // async, не блокирует
}
return old;
7. Безопасность
- Никогда permission-флаги на клиенте без серверной проверки (клиент может перевернуть)
- Аудит-лог изменений (кто, когда, для кого)
- Право менять prod-флаг — только on-call / релиз-инженер
- Защита от "случайно включил для всех": требуй явное подтверждение для >50%
8. Тестирование
- Unit-тесты обоих веток (не только enabled)
- Integration: матрица для критичных флагов (on/off)
- E2E: главный flow с дефолтными значениями
- Перед удалением флага — поиск всех call sites и тестов с явным значением
9. Удаление (cleanup)
Самая забываемая часть. Без этого код превращается в флагово-зависимое спагетти.
Процесс:
- Подтвердить: 100% rollout уже N недель, метрики стабильны
- Удалить проигравшую ветку
- Удалить вызов
flags.isOn(...) - Удалить флаг в провайдере
- Удалить тесты для отключённой ветки
Tooling: regex по кодовой базе flags\.(isOn|boolean)\('(.+?)' → diff с активными в провайдере → orphans.
10. Анти-паттерны
- Флаги без owner — никто не знает зачем
- Флаги без TTL — живут вечно
- Permission-флаги через release-механизм (нужен деплой чтобы выдать доступ)
- Глубокая вложенность:
if (a) if (b) if (c)— комбинаторный взрыв веток - Использовать флаг для config'а (URL, лимит) — это конфиг, не флаг
- Один флаг шарится между фронтом и бэком с разной логикой
- Hardcoded флаги в коде на "ну я потом удалю"
В конце
- Карта типов флагов и где какой используется
- Способ хранения и оценки (одна технология или гибрид)
- Конвенция именования (
<team>.<area>.<purpose>) - Lifecycle и процесс cleanup
- SLO для оценки (latency, fallback rate)
- Метрики: число флагов, средний возраст, orphans
Multi-agent: координатор и специалисты
Архитектура из координатора и специализированных агентов: передача контекста, дедупликация, race conditions.
Новый subagent или новый skill: что выбрать
Decision tree: создавать ли отдельного агента или достаточно skill. Критерии — контекст, переиспользование, frequency, complexity.
Architecture Decision Record (ADR)
Зафиксировать архитектурное решение: контекст, варианты, выбор и trade-offs.