Skip to content

Feature Flags

AnderProgramming edited this page Jul 17, 2026 · 4 revisions

Los Feature Flags (o feature toggles) son una técnica que permite desacoplar el despliegue de código del lanzamiento de funcionalidades. Linker1 utiliza LaunchDarkly como plataforma de gestión de feature flags.


¿Qué es LaunchDarkly?

LaunchDarkly es una plataforma de feature management que permite:

  • Activar/desactivar features en tiempo real sin redeployar
  • Targeting por usuario o segmento: habilitar features para grupos específicos
  • Rollout gradual: liberar features al 10%, 50%, 100% de los usuarios
  • Kill switch instantáneo: deshabilitar una feature en segundos si causa problemas

Linker1 usa el Java Server SDK (v7.8.0) para evaluación server-side de flags.


Arquitectura

La integración con LaunchDarkly sigue el mismo patrón de Inyección de Dependencias (DI) e Inversión de Control (IoC) usado en todo el proyecto:

Main (Composition Root)
 │
 ├── LDClient (LaunchDarkly SDK)
 │       │
 │       ▼
 ├── FeatureFlags(ldClient)     ← ÚNICO punto de contacto con el SDK
 │       │
 │       ▼
 └── StaticRoutes(featureFlags) ← Consume flags sin saber del SDK

Reglas arquitectónicas

  1. FeatureFlags es la ÚNICA clase que importa o interactúa con el SDK de LaunchDarkly
  2. Los consumidores (e.g., StaticRoutes) reciben FeatureFlags por constructor y llaman métodos simples como isNewUiEnabled()
  3. Los tests inyectan un FeatureFlags con valores predefinidos — sin conexión al SDK

Flag Implementado: new-ui

Propósito

Controlar qué versión de la interfaz web se sirve a los usuarios:

Flag State Interfaz Archivos
OFF (default) V1 — Interfaz original v1/index.html + v1/styles.css
ON V2 — Nueva interfaz "Colombia Edition" v2/index-v2.html + v2/styles2.css

Implementación en código

FeatureFlags.java — Evaluación del flag:

public boolean isNewUiEnabled() {
    if (ldClient == null) {
        return false;  // safe default si no hay conexión
    }
    try {
        LDContext context = LDContext.builder("anonymous-user")
            .anonymous(true)
            .build();
        return ldClient.boolVariation("new-ui", context, false);
    } catch (Exception e) {
        return false;  // fail-safe: si hay error, servir la UI estable
    }
}

StaticRoutes.java — Consumo del flag:

app.get("/", ctx -> {
    String path = featureFlags.isNewUiEnabled() 
        ? "/v2/index-v2.html"   // nueva UI
        : "/v1/index.html";      // UI original
    serve(ctx, path, "text/html", notFound);
});

app.get("/styles.css", ctx -> {
    String path = featureFlags.isNewUiEnabled() 
        ? "/v2/styles2.css"     // estilos nuevos
        : "/v1/styles.css";      // estilos originales
    serve(ctx, path, "text/css", notFound);
});

Configuración

Variable de entorno Propósito Obligatoria
LD_SDK_KEY Clave del SDK de LaunchDarkly — la app no arranca sin ella
LD_OFFLINE Modo offline para testing local No (default: false)

Si LD_SDK_KEY no está definida, Main.java ejecuta System.exit(1):

String ldSdkKey = System.getenv("LD_SDK_KEY");
if (ldSdkKey == null || ldSdkKey.isBlank()) {
    System.err.println("ERROR: The LD_SDK_KEY environment variable is not set.");
    System.exit(1);
}

El modo offline (LD_OFFLINE=true) permite ejecutar la app sin conectar al servicio de LaunchDarkly (todos los flags retornan su valor default).


Estrategia de Rollout

Cada nueva feature que use feature flags debe seguir este proceso:

┌─────────────────────────────────────────────────────────────────┐
│  1. DEVELOP     Implementar la feature envuelta en un flag      │
│                 (flag OFF por defecto)                          │
│                                                                 │
│  2. DEPLOY      Desplegar a producción con el flag OFF          │
│                 (código nuevo, feature invisible)               │
│                                                                 │
│  3. TEST        Habilitar para testing interno                  │
│                 (targeting a developers/QA)                     │
│                                                                 │
│  4. CANARY      Habilitar para 10% de usuarios                  │
│                 (progressive delivery)                          │
│                                                                 │
│  5. ROLLOUT     Habilitar para 100%                             │
│                 (feature fully released)                        │
│                                                                 │
│  6. CLEANUP     Eliminar código legacy + flag del codebase      │
│                 y del dashboard de LaunchDarkly                 │
└─────────────────────────────────────────────────────────────────┘

Fase de Transición y Cleanup

Durante la transición de la UI V1 a V2:

  • Ambas versiones coexisten en public/v1/ y public/v2/
  • El flag new-ui decide cuál servir en cada request
  • app.js es compartido (no depende del flag)

Una vez validada la V2 en producción al 100%:

  1. Eliminar v1/index.html y v1/styles.css
  2. Renombrar v2/index-v2.htmlindex.html y v2/styles2.cssstyles.css
  3. Eliminar la lógica condicional en StaticRoutes.java
  4. Eliminar el check featureFlags.isNewUiEnabled() y su implementación
  5. Archivar el flag new-ui en LaunchDarkly

Si no se hace el cleanup, el flag viejo se convierte en deuda técnica: código muerto que confunde, bifurcaciones que nadie entiende, y tests adicionales que mantener.


Beneficios Demostrados

Beneficio Cómo se evidencia en Linker1
Releases seguros La V2 se despliega con el flag OFF, sin riesgo
Rollback instantáneo Un toggle en el dashboard revierte la feature en segundos
Testing en producción La V2 se puede probar con tráfico real antes del lanzamiento
Bajo acoplamiento StaticRoutes no sabe que LaunchDarkly existe
Testabilidad Tests unitarios inyectan un FeatureFlags mock
Continuous Delivery El código se mergea a main continuamente, protegido por el flag

Feature Flags Futuros

A medida que la aplicación evolucione, se planean nuevos flags:

Flag Propósito
strict-url-validation Validación más estricta de URLs
custom-alias Disponibilidad de aliases personalizados
experimental-api Nuevos endpoints REST para early adopters

Configuración de Desarrollo Local

Para ejecutar la app localmente con feature flags:

Option 1: Con LaunchDarkly (requiere SDK key)

export LD_SDK_KEY="sdk-xxxxxxxx"
java -jar linker1-1.0-jar-with-dependencies.jar

Option 2: Modo Offline (sin conexión a LaunchDarkly)

export LD_SDK_KEY="fake-key"
export LD_OFFLINE="true"
java -jar linker1-1.0-jar-with-dependencies.jar

En modo offline, todos los flags retornan su valor default (false para new-ui).

VS Code launch.json:

{
    "type": "java",
    "name": "Launch Linker (Main)",
    "request": "launch",
    "mainClass": "Main",
    "env": {
        "LD_SDK_KEY": "sdk-xxxxxxxx"
    }
}

Clone this wiki locally