Avaliação
Endpoints para avaliação de feature flags em tempo real.
Endpoints
| Método | Endpoint | Descrição | Permissão |
|---|---|---|---|
| POST | /api/v1/feature-flags/evaluate/:flagKey | Avaliar uma flag | FEATURE_FLAGS_EVALUATE |
| POST | /api/v1/feature-flags/evaluate-batch | Avaliar multiplas flags | FEATURE_FLAGS_EVALUATE |
| POST | /api/v1/feature-flags/evaluate-all | Avaliar todas as flags ativas | FEATURE_FLAGS_EVALUATE |
Contexto de Avaliação
O contexto e enviado no body de todas as requisições de avaliação:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
context.userId | string | Não | ID do usuário (usado para rollout percentual e overrides) |
context.attributes | object | Não | Atributos do usuário para matching de segmentos |
Resposta de Avaliação
| Campo | Tipo | Descrição |
|---|---|---|
flagKey | string | Key da flag avaliada |
value | any | Valor da variação retornada |
variationKey | string | Key da variação retornada |
reason | enum | Motivo da decisão: DISABLED, DEFAULT, OVERRIDE, RULE_MATCH |
matchedRuleId | string | ID da targeting rule que fez match (se aplicável) |
matchedSegmentIds | array | IDs dos segmentos que fizeram match (se aplicável) |
Avaliar Uma Flag
POST /api/v1/feature-flags/evaluate/:flagKey
Avalia uma unica feature flag para o contexto fornecido.
Request
- cURL
- JavaScript
curl -X POST 'https://feature-flags.stg.catalisa.app/api/v1/feature-flags/evaluate/new-checkout-flow' \
-H 'Authorization: Bearer SEU_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"context": {
"userId": "user-123",
"attributes": {
"plan": "premium",
"country": "BR",
"age": 25
}
}
}'
const flagKey = 'new-checkout-flow';
const response = await fetch(`https://feature-flags.stg.catalisa.app/api/v1/feature-flags/evaluate/${flagKey}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
context: {
userId: 'user-123',
attributes: {
plan: 'premium',
country: 'BR',
age: 25,
},
},
}),
});
const { data } = await response.json();
console.log(`Feature enabled: ${data.value}`);
console.log(`Reason: ${data.reason}`);
Response (200 OK)
{
"data": {
"flagKey": "new-checkout-flow",
"value": true,
"variationKey": "on",
"reason": "RULE_MATCH",
"matchedRuleId": "550e8400-e29b-41d4-a716-446655440002",
"matchedSegmentIds": ["550e8400-e29b-41d4-a716-446655440001"]
}
}
Avaliar Multiplas Flags (Batch)
POST /api/v1/feature-flags/evaluate-batch
Avalia multiplas flags de uma vez para o mesmo contexto. Máximo de 50 flags por requisição.
Request
- cURL
- JavaScript
curl -X POST 'https://feature-flags.stg.catalisa.app/api/v1/feature-flags/evaluate-batch' \
-H 'Authorization: Bearer SEU_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"flagKeys": ["new-checkout-flow", "dark-mode", "premium-features"],
"context": {
"userId": "user-123",
"attributes": {
"plan": "premium"
}
}
}'
const response = await fetch('https://feature-flags.stg.catalisa.app/api/v1/feature-flags/evaluate-batch', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
flagKeys: ['new-checkout-flow', 'dark-mode', 'premium-features'],
context: {
userId: 'user-123',
attributes: {
plan: 'premium',
},
},
}),
});
const { data } = await response.json();
// data é um array de resultados
data.forEach((result) => {
console.log(`${result.flagKey}: ${result.value} (${result.reason})`);
});
Response (200 OK)
{
"data": [
{
"flagKey": "new-checkout-flow",
"value": true,
"variationKey": "on",
"reason": "RULE_MATCH",
"matchedRuleId": "550e8400-e29b-41d4-a716-446655440002"
},
{
"flagKey": "dark-mode",
"value": false,
"variationKey": "off",
"reason": "DEFAULT"
},
{
"flagKey": "premium-features",
"value": { "discount": 0.15, "freeShipping": true },
"variationKey": "premium",
"reason": "RULE_MATCH"
}
]
}
Avaliar Todas as Flags
POST /api/v1/feature-flags/evaluate-all
Avalia todas as flags ativas da organização para o contexto fornecido.
Request
- cURL
- JavaScript
curl -X POST 'https://feature-flags.stg.catalisa.app/api/v1/feature-flags/evaluate-all' \
-H 'Authorization: Bearer SEU_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"context": {
"userId": "user-123",
"attributes": {
"plan": "premium"
}
}
}'
const response = await fetch('https://feature-flags.stg.catalisa.app/api/v1/feature-flags/evaluate-all', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
context: {
userId: 'user-123',
attributes: {
plan: 'premium',
},
},
}),
});
const { data } = await response.json();
// Criar mapa de flags para acesso facil
const flags = data.reduce((acc, flag) => {
acc[flag.flagKey] = flag.value;
return acc;
}, {});
console.log(flags['new-checkout-flow']); // true
console.log(flags['dark-mode']); // false
Response (200 OK)
{
"data": [
{
"flagKey": "new-checkout-flow",
"value": true,
"variationKey": "on",
"reason": "RULE_MATCH"
},
{
"flagKey": "dark-mode",
"value": false,
"variationKey": "off",
"reason": "DEFAULT"
},
{
"flagKey": "premium-features",
"value": { "discount": 0.15, "freeShipping": true },
"variationKey": "premium",
"reason": "RULE_MATCH"
}
]
}
Reasons (Motivos de Decisão)
| Reason | Descrição |
|---|---|
DISABLED | Flag esta desativada, retornou default variation |
DEFAULT | Nenhuma targeting rule fez match, retornou default variation |
OVERRIDE | Override configurado para este usuário |
RULE_MATCH | Uma targeting rule fez match |
Lógica de Avaliação
Fluxo de Decisão da Avaliação
A avaliação de uma feature flag segue este fluxo sequencial:
1. Verificar se a flag está desativada
- Se SIM: Retornar
defaultVariationcomreason: DISABLED - Se NÃO: Continuar para próximo passo
2. Verificar se existe override para o usuário
- Se SIM: Retornar variação do override com
reason: OVERRIDE - Se NÃO: Continuar para próximo passo
3. Avaliar targeting rules
- Iterar pelas targeting rules por ordem de prioridade (menor primeiro)
- Para cada rule:
-
3.1. Verificar se a rule faz match
- Avaliar se usuário pertence aos segmentos da rule
- Se NÃO faz match: Continuar para próxima rule
- Se faz match: Continuar para passo 3.2
-
3.2. Aplicar rollout percentual
- Calcular hash consistente do
userId - Verificar se hash está dentro do percentual definido
- Se passou no rollout: Retornar variação da rule com
reason: RULE_MATCH - Se não passou: Continuar para próxima rule
- Calcular hash consistente do
-
4. Nenhuma rule fez match
- Retornar
defaultVariationcomreason: DEFAULT
Resumo dos Reasons:
DISABLED: Flag desativadaOVERRIDE: Override configurado para este usuárioRULE_MATCH: Targeting rule fez match e passou no rolloutDEFAULT: Nenhuma rule fez match ou flag não tem rules
Cache e Performance
Cache de Avaliação
- Resultados são cacheados por 5 minutos (300 segundos)
- Cache key:
ff:eval:{organizationId}:{flagKey}:{hash(context)} - Cache invalidado automaticamente ao atualizar flag, rules ou overrides
Cache de Segment Membership
- Membership de segmentos cacheado por 15 minutos (900 segundos)
- Cache key:
ff:membership:{organizationId}:{segmentId}:{userId} - Cache invalidado ao atualizar segmento
Melhores Praticas
// ✅ BOM: Buscar todas as flags de uma vez no inicio da sessao
const flags = await evaluateAll({ context });
// ❌ RUIM: Avaliar flag a flag durante a sessao
if (await evaluateFlag('flag-1', context)) { ... }
if (await evaluateFlag('flag-2', context)) { ... }
if (await evaluateFlag('flag-3', context)) { ... }
// ✅ BOM: Usar evaluate-batch para um conjunto conhecido de flags
const flags = await evaluateBatch({
flagKeys: ['feature-a', 'feature-b', 'feature-c'],
context,
});
// ✅ BOM: Incluir userId para rollout consistente
const flags = await evaluateAll({
context: {
userId: currentUser.id, // Garante consistencia
attributes: { plan: currentUser.plan },
},
});
Exemplos de Uso
Bootstrap de Aplicacao Frontend
// No carregamento da aplicacao
async function loadFeatureFlags(userId, userPlan) {
const response = await fetch('/feature-flags/api/v1/feature-flags/evaluate-all', {
method: 'POST',
headers: {
'Authorization': `Bearer ${getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
context: {
userId,
attributes: { plan: userPlan },
},
}),
});
const { data } = await response.json();
// Armazenar em estado global
return data.reduce((flags, item) => {
flags[item.flagKey] = item.value;
return flags;
}, {});
}
// Uso no componente
function CheckoutButton() {
const flags = useFeatureFlags();
if (flags['new-checkout-flow']) {
return <NewCheckout />;
}
return <LegacyCheckout />;
}
Backend Feature Toggle
// Service layer
async function processPayment(userId, order) {
const { data } = await evaluateFlag('pix-payment-enabled', {
context: {
userId,
attributes: {
orderValue: order.total,
country: order.shippingCountry,
},
},
});
if (data.value === true) {
return processWithPix(order);
}
return processWithCreditCard(order);
}
A/B Testing
// Avaliar variante do experimento
const { data } = await evaluateFlag('checkout-button-experiment', {
context: {
userId: 'user-123',
},
});
// data.value pode ser "green", "blue", ou "red"
// data.variationKey indica qual variante foi atribuida
// Registrar em analytics
analytics.track('experiment_viewed', {
experimentId: 'checkout-button-experiment',
variant: data.variationKey,
userId: 'user-123',
});
Erros Comuns
| Código | Erro | Descrição |
|---|---|---|
| 404 | NOT_FOUND | Flag nao encontrada (evaluate single) |
| 400 | VALIDATION | flagKeys vazio ou excede limite de 50 (batch) |