Feature flags e deploy progressivo no frontend
Feature flags (também chamadas de feature toggles) são uma técnica poderosa que permite ativar ou desativar funcionalidades em tempo de execução sem precisar fazer um novo deploy. No contexto de JavaScript, Node.js e React, essa abordagem viabiliza deploys progressivos, testes A/B e rollbacks instantâneos — tudo controlado por configuração remota.
1. Fundamentos de Feature Flags no Frontend
Existem quatro tipos principais de feature flags:
- Release flags: controlam quando uma funcionalidade entra em produção
- Experiment flags: usadas para testes A/B e experimentos
- Ops flags: gerenciam aspectos operacionais (manutenção, degradação)
- Permission flags: liberam acesso baseado em papéis de usuário
Os benefícios são claros: deploys contínuos com baixo risco, rollback instantâneo sem reverter código, e capacidade de testar com usuários reais. Porém, há riscos: dívida técnica quando flags não são removidas, complexidade de código com condicionais espalhadas, e potencial impacto na performance se mal implementadas.
2. Arquitetura de um Sistema de Feature Flags em React
Vamos construir um sistema simples usando React Context e um hook customizado:
// featureFlagsContext.js
import React, { createContext, useContext, useReducer, useEffect } from 'react';
const FeatureFlagsContext = createContext();
const initialState = {
flags: {},
loading: true,
error: null
};
function flagsReducer(state, action) {
switch (action.type) {
case 'SET_FLAGS':
return { ...state, flags: action.payload, loading: false };
case 'SET_ERROR':
return { ...state, error: action.payload, loading: false };
default:
return state;
}
}
export function FeatureFlagsProvider({ children }) {
const [state, dispatch] = useReducer(flagsReducer, initialState);
useEffect(() => {
fetch('/api/flags')
.then(res => res.json())
.then(data => dispatch({ type: 'SET_FLAGS', payload: data }))
.catch(err => dispatch({ type: 'SET_ERROR', payload: err.message }));
}, []);
return (
<FeatureFlagsContext.Provider value={state}>
{children}
</FeatureFlagsContext.Provider>
);
}
export function useFeatureFlag(flagName) {
const { flags, loading } = useContext(FeatureFlagsContext);
return { isEnabled: flags[flagName] || false, loading };
}
O hook useFeatureFlag permite que qualquer componente consuma flags de forma declarativa:
// Dashboard.js
import { useFeatureFlag } from './featureFlagsContext';
export default function Dashboard() {
const { isEnabled: showNewChart, loading } = useFeatureFlag('new_chart');
if (loading) return <div>Carregando...</div>;
return (
<div>
<h1>Dashboard</h1>
{showNewChart ? <NewChart /> : <OldChart />}
</div>
);
}
Para integração com serviços externos como LaunchDarkly ou Unleash, basta substituir o fetch no useEffect pela SDK correspondente.
3. Implementação Prática com Node.js Backend
O backend em Node.js serve como fonte de verdade para as flags. Vamos criar um endpoint com cache Redis:
// server.js
const express = require('express');
const Redis = require('ioredis');
const app = express();
const redis = new Redis();
const CACHE_TTL = 60; // 1 minuto
async function getUserFlags(userId) {
const cacheKey = `flags:user:${userId}`;
// Tenta cache primeiro
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// Se não estiver em cache, busca no banco
const flags = await db.query(
'SELECT flag_name, enabled FROM user_flags WHERE user_id = $1',
[userId]
);
// Popula cache
await redis.setex(cacheKey, CACHE_TTL, JSON.stringify(flags));
return flags;
}
app.get('/api/flags', async (req, res) => {
const userId = req.user.id; // middleware de autenticação
const flags = await getUserFlags(userId);
res.json(flags);
});
Um middleware de autorização pode filtrar flags por grupo de usuário:
function authorizeFlags(req, res, next) {
const userGroup = req.user.group; // 'beta', 'admin', 'regular'
const allowedFlags = {
beta: ['new_dashboard', 'experimental_api'],
admin: ['all_features'],
regular: ['stable_features']
};
req.allowedFlags = allowedFlags[userGroup] || [];
next();
}
4. Estratégias de Deploy Progressivo
O deploy progressivo combina feature flags com distribuição controlada. Exemplo de canary release:
// canaryService.js
function shouldEnableCanary(userId, percentage = 10) {
const hash = hashCode(userId.toString());
return (hash % 100) < percentage;
}
// Uso no backend
app.get('/api/flags', async (req, res) => {
const flags = {};
if (shouldEnableCanary(req.user.id, 10)) {
flags.new_feature = true;
}
// Segmentação por geografia
if (req.user.country === 'BR') {
flags.pix_payment = true;
}
res.json(flags);
});
Para rollback automático, integre com monitoramento:
// monitor.js
async function checkErrorRate() {
const errorRate = await getErrorRate('new_feature');
if (errorRate > 0.05) { // 5% de erros
await disableFlag('new_feature');
await notifyTeam('Rollback automático ativado');
}
}
5. Code Splitting Orientado por Feature Flags
Combinar feature flags com React.lazy reduz o payload inicial:
// App.js
import { useFeatureFlag } from './featureFlagsContext';
import { lazy, Suspense } from 'react';
const NewDashboard = lazy(() => import('./NewDashboard'));
const OldDashboard = lazy(() => import('./OldDashboard'));
export default function App() {
const { isEnabled: useNewDashboard } = useFeatureFlag('new_dashboard');
return (
<Suspense fallback={<div>Carregando...</div>}>
{useNewDashboard ? <NewDashboard /> : <OldDashboard />}
</Suspense>
);
}
Isso permite que o bundle do NewDashboard só seja baixado quando a flag estiver ativa, economizando largura de banda para a maioria dos usuários.
6. Testes e Qualidade com Feature Flags
Testes unitários com Jest e mocks de flags:
// Dashboard.test.js
import { render, screen } from '@testing-library/react';
import Dashboard from './Dashboard';
jest.mock('./featureFlagsContext', () => ({
useFeatureFlag: jest.fn()
}));
test('mostra novo gráfico quando flag está ativa', () => {
useFeatureFlag.mockReturnValue({ isEnabled: true, loading: false });
render(<Dashboard />);
expect(screen.getByText('Novo Gráfico')).toBeInTheDocument();
});
test('mostra gráfico antigo quando flag está inativa', () => {
useFeatureFlag.mockReturnValue({ isEnabled: false, loading: false });
render(<Dashboard />);
expect(screen.getByText('Gráfico Antigo')).toBeInTheDocument();
});
Para testes de integração, use matrix testing para cobrir combinações:
const flagCombinations = [
{ new_chart: true, dark_mode: false },
{ new_chart: false, dark_mode: true },
{ new_chart: true, dark_mode: true },
{ new_chart: false, dark_mode: false }
];
flagCombinations.forEach(flags => {
test(`dashboard com flags ${JSON.stringify(flags)}`, async () => {
// Configura mock das flags e renderiza
});
});
7. Boas Práticas e Armadilhas Comuns
Limpeza de flags obsoletas: crie um processo para remover flags após estabilização. Use uma task no CI que alerta sobre flags com mais de 30 dias.
Evitar aninhamento excessivo:
// Ruim
{flagA && flagB && flagC ? <Feature /> : null}
// Bom
const shouldShowFeature = flagA && flagB && flagC;
{shouldShowFeature && <Feature />}
Logging e auditoria: registre cada decisão de feature flag para debug:
function useFeatureFlag(flagName) {
const { flags, loading } = useContext(FeatureFlagsContext);
const isEnabled = flags[flagName] || false;
useEffect(() => {
if (!loading) {
logger.info(`Feature flag ${flagName} resolved to ${isEnabled}`);
}
}, [flagName, isEnabled, loading]);
return { isEnabled, loading };
}
Feature flags bem implementadas transformam a forma como entregamos software. Elas permitem que times de frontend façam deploys com confiança, experimentem com segurança e revertam rapidamente quando necessário. A chave está em manter a disciplina de remover flags antigas e monitorar constantemente o impacto no desempenho e na experiência do usuário.
Referências
- LaunchDarkly Documentation - Feature Flags — Documentação oficial da plataforma líder em feature flags, com guias de implementação em React e Node.js
- Unleash - Open Source Feature Management — Plataforma open source de feature flags com SDKs para JavaScript e React
- Martin Fowler - Feature Toggles — Artigo seminal sobre feature toggles, abordando categorias, técnicas e armadilhas
- React Documentation - Code Splitting — Documentação oficial sobre React.lazy e Suspense para carregamento lazy de componentes
- Feature Flags in Node.js - LogRocket Blog — Tutorial prático de implementação de feature flags em aplicações Node.js com Express
- Progressive Delivery with Feature Flags - Split.io — Guia sobre deploy progressivo e canary releases usando feature flags