Skip to content
PПромтбук
RUEN
04Архитектура

Архитектура feature flags

Типы флагов (release/experiment/ops/permission), хранение, оценка, тех-долг и удаление.

Спроектируй систему feature flags для контекста: {{context}}.

Принцип: флаг — это код. У него есть владелец, срок жизни и план удаления. Без этого вы получаете 2000 флагов через 2 года.

1. Типы флагов (важно различать)

Каждый тип требует своего хранения, инвалидации и lifecycle:

ТипНазначениеLifetimeАудитория
ReleaseСпрятать незавершённую фичудни-неделивесь трафик
ExperimentA/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)

Самая забываемая часть. Без этого код превращается в флагово-зависимое спагетти.

Процесс:

  1. Подтвердить: 100% rollout уже N недель, метрики стабильны
  2. Удалить проигравшую ветку
  3. Удалить вызов flags.isOn(...)
  4. Удалить флаг в провайдере
  5. Удалить тесты для отключённой ветки

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
К подразделу «Архитектура»
Похожие промты