Feature Flags sem Vendor Lock-in: Implementação Própria com TypeScript e Zero Dependência de SaaS
O problema real: sua release depende de um SDK que você não controla
Feature flags resolvem um problema concreto: separar deploy de release. Você faz merge na main, o código vai para produção, mas a funcionalidade só aparece para quem você decide. O problema começa quando a implementação amarra o código inteiro a um provider específico.
LaunchDarkly cobra por seat e por flag evaluation. Unleash tem versão open source, mas o hosted exige plano pago para funcionalidades como métricas e audit log. Split, Flagsmith, ConfigCat: cada um com SDK próprio, formato de configuração próprio e API própria. Trocar de provider significa reescrever cada ponto do código onde uma flag é avaliada.
A solução é uma camada de abstração fina entre o código de negócio e o mecanismo de avaliação. Não é over-engineering: são menos de 200 linhas de código que eliminam a dependência direta de qualquer vendor.
Arquitetura: porta e adaptador para flags
O padrão é simples. Uma interface define o contrato. Adaptadores implementam essa interface para cada backend (JSON local, banco de dados, Redis, variáveis de ambiente, ou qualquer SaaS). O código de negócio depende só da interface.
// src/flags/types.ts
// FlagValue aceita os tipos reais que flags assumem em produção.
// Booleano cobre toggle simples. String cobre variantes (A/B test).
// Number cobre rollout percentual.
export type FlagValue = boolean | string | number;
export interface FlagContext {
userId?: string;
email?: string;
country?: string;
plan?: "free" | "pro" | "enterprise";
[key: string]: unknown;
}
export interface FlagProvider {
get(flagName: string, context?: FlagContext): Promise<FlagValue>;
getAllFlags(): Promise<Record<string, FlagValue>>;
dispose(): Promise<void>;
}
A interface FlagProvider tem três métodos. get avalia uma flag para um contexto. getAllFlags retorna o estado completo (útil para debug e dashboards internos). dispose libera conexões, algo que providers baseados em Redis ou WebSocket precisam.
Adaptador 1: JSON local para desenvolvimento
O adaptador mais simples lê de um arquivo JSON. Serve para desenvolvimento local e para testes de integração onde você quer controle total sobre o estado das flags.
// src/flags/providers/json-provider.ts
import { readFile } from "node:fs/promises";
import type { FlagProvider, FlagValue, FlagContext } from "../types.js";
export class JsonFlagProvider implements FlagProvider {
private flags: Record<string, FlagValue> = {};
private loaded = false;
constructor(private readonly filePath: string) {}
private async load(): Promise<void> {
if (this.loaded) return;
const raw = await readFile(this.filePath, "utf-8");
this.flags = JSON.parse(raw);
this.loaded = true;
}
async get(flagName: string, _context?: FlagContext): Promise<FlagValue> {
await this.load();
// Retorna false para flags inexistentes em vez de undefined.
// Flag inexistente = funcionalidade desligada. Fail-safe.
return this.flags[flagName] ?? false;
}
async getAllFlags(): Promise<Record<string, FlagValue>> {
await this.load();
return { ...this.flags };
}
async dispose(): Promise<void> {
this.flags = {};
this.loaded = false;
}
}
O arquivo JSON correspondente:
{
"new_checkout_flow": true,
"dark_mode": false,
"max_upload_size_mb": 50,
"pricing_variant": "control"
}
Adaptador 2: variáveis de ambiente para containers
Em ambientes containerizados, variáveis de ambiente são o mecanismo de configuração mais portável. Funciona com Docker, Kubernetes ConfigMaps, Cloudflare Workers e qualquer plataforma serverless.
// src/flags/providers/env-provider.ts
import type { FlagProvider, FlagValue, FlagContext } from "../types.js";
// Prefixo evita colisão com variáveis do sistema.
const FLAG_PREFIX = "FF_";
function parseValue(raw: string): FlagValue {
if (raw ===
---
Leia o artigo completo em [https://www.vivodecodigo.com.br/backend/feature-flags-sem-vendor-lock-in-typescript-implementacao-propria](https://www.vivodecodigo.com.br/backend/feature-flags-sem-vendor-lock-in-typescript-implementacao-propria)