Современный подход к безопасным релизам в production подразумевает, что новый код должен прибыть на сервер, но оставаться неактивным. Именно эту парадигму реализуют feature flags — механизмы, которые разделяют развертывание кода и активацию функциональности. Они позволяют выпускать обновления для ограниченной группы пользователей, проводить A/B-тестирование и мгновенно отключать проблемные функции без необходимости повторного деплоя.
Принципиальное отличие в процессе деплоя
В классическом сценарии инцидент в production развивается по шаблону: выкатывается новая версия, что-то ломается, команда откатывается к предыдущему стабильному билду. Этот процесс отката занимает от 5 до 15 минут, в течение которых пользователи сталкиваются с ошибками.
Feature flags кардинально меняют эту ситуацию. При возникновении проблемы вы просто отключаете флаг — функциональность становится недоступной мгновенно. Откат происходит без передеплоя, без ожидания выполнения пайплайна, без паники в рабочих чатах. Сам код остается на серверах, но перестает выполняться.
Помимо быстрых откатов, система флагов открывает несколько стратегически важных возможностей:
- Канареечные релизы: Активация функции для 1% пользователей с последующим мониторингом метрик и постепенным расширением аудитории.
- A/B-тестирование: Направление 50% трафика на каждую из вариантов реализации с измерением конверсии.
- Бета-программы: Включение функциональности для конкретных пользовательских ID или аккаунтов.
- Аварийные выключатели: Отключение проблемных интеграций без изменений кода.
- Операционные тогглы: Деактивация ресурсоемких функций при высокой нагрузке.
- Разработка в основной ветке: Возможность помещать незавершенные функции за флагом, избегая создания долгоживущих веток.
LaunchDarkly: комплексное Enterprise-решение
LaunchDarkly представляет собой наиболее функционально насыщенный управляемый сервис для работы с feature flags. Платформа предоставляет расширенные возможности: правила таргетирования, процентные роуллауты, мультивариативные флаги и обновления в реальном времени без необходимости постоянного опроса сервера.
Установка SDK выполняется стандартным способом:
npm install @launchdarkly/node-server-sdk
Инициализация клиента требует минимальной настройки:
// flags/launchdarkly.js
const { init } = require('@launchdarkly/node-server-sdk');
let ldClient;
async function getClient() {
if (ldClient) return ldClient;
ldClient = init(process.env.LAUNCHDARKLY_SDK_KEY);
await ldClient.waitForInitialization();
return ldClient;
}
После инициализации проверка состояния флага становится тривиальной операцией:
async function isFeatureEnabled(featureKey, userContext) {
const client = await getClient();
return client.variation(featureKey, userContext, false);
}
// Использование в обработчике запроса
app.get('/new-feature', async (req, res) => {
const user = { key: req.user.id };
if (await isFeatureEnabled('new-ui', user)) {
// Рендер новой версии интерфейса
} else {
// Рендер старой версии
}
});
Основное преимущество LaunchDarkly — централизованное управление флагами через веб-интерфейс с возможностью тонкой настройки правил активации для разных сегментов пользователей. Изменения применяются мгновенно без перезапуска приложения.
Unleash: самодостаточная open-source альтернатива
Для команд, предпочитающих полный контроль над инфраструктурой, Unleash предлагает мощную open-source платформу для управления feature flags. Решение можно развернуть на собственных серверах, сохраняя при этом богатый функционал, сравнимый с коммерческими аналогами.
Установка клиентской библиотеки:
npm install unleash-client
Настройка подключения к серверу Unleash:
// flags/unleash.js
const { initialize } = require('unleash-client');
const unleash = initialize({
url: process.env.UNLEASH_URL,
appName: 'my-node-app',
instanceId: process.env.INSTANCE_ID || 'default',
});
unleash.on('ready', () => {
console.log('Unleash client ready');
});
module.exports = unleash;
Проверка активации функции с контекстом пользователя:
const unleash = require('./flags/unleash');
function shouldShowNewDashboard(userId) {
const context = {
userId: userId,
// Дополнительные атрибуты для сегментации
};
return unleash.isEnabled('new-dashboard', context);
}
Unleash поддерживает продвинутые стратегии активации, включая постепенный роуллаут, таргетирование по пользовательским атрибутам и комбинирование нескольких условий. Архитектура системы предполагает наличие отдельного сервера Unleash, который может быть развернут как в виде Docker-контейнера, так и на Kubernetes.
Кастомная реализация: минималистичный подход
Для проектов, где внедрение сторонних решений неоправданно или требуется максимальная простота, можно реализовать собственную систему feature flags буквально за несколько часов. Такой подход исключает внешние зависимости и дает полный контроль над логикой работы.
Базовая реализация может использовать in-memory хранилище с возможностью горячей перезагрузки конфигурации:
// flags/custom.js
class FeatureFlagManager {
constructor() {
this.flags = new Map();
this.listeners = new Set();
this.loadFlags();
}
loadFlags() {
// Загрузка флагов из БД, файла конфигурации или удаленного источника
this.flags.set('new-checkout', {
enabled: true,
percentage: 30, // Включено для 30% пользователей
userIds: ['beta-user-1', 'beta-user-2'],
});
}
isEnabled(flagName, userId = null) {
const flag = this.flags.get(flagName);
if (!flag) return false;
if (!flag.enabled) return false;
// Проверка явного списка пользователей
if (userId && flag.userIds && flag.userIds.includes(userId)) {
return true;
}
// Процентный роуллаут на основе хеша userId
if (flag.percentage && userId) {
const hash = this.stringHash(userId);
return (hash % 100) < flag.percentage;
}
return flag.enabled;
}
stringHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0; // Преобразование в 32-битное целое
}
return Math.abs(hash);
}
updateFlag(flagName, config) {
this.flags.set(flagName, config);
this.notifyListeners(flagName);
}
onUpdate(listener) {
this.listeners.add(listener);
}
notifyListeners(flagName) {
this.listeners.forEach(listener => listener(flagName));
}
}
module.exports = new FeatureFlagManager();
Интеграция с приложением становится предельно простой:
const featureFlags = require('./flags/custom');
// В обработчике маршрута
app.get('/experimental-feature', (req, res) => {
const userId = req.user?.id || req.ip;
if (featureFlags.isEnabled('experimental-flow', userId)) {
// Новая экспериментальная логика
} else {
// Стандартная логика
}
});
Для production-использования кастомную реализацию стоит расширить персистентным хранением конфигурации, механизмом кэширования и API для удаленного управления флагами. Простейшим вариантом может стать хранение настроек в Redis с подпиской на обновления через Pub/Sub.
Выбор конкретного подхода зависит от масштаба проекта, требований к инфраструктуре и бюджета. LaunchDarkly предлагает готовое решение с максимальным комфортом, Unleash балансирует между функциональностью и контролем, а кастомная реализация обеспечивает минимализм и полную независимость.