Módulo 2: Feature Flags

Deploy vs. release, resuelto en código: el feature flag

Descripción

El módulo 1 nombró la distinción: deploy es que el código de recommendations esté corriendo en los servidores de Mercado; release es que un comprador real lo vea. Esta lección da el paso que el módulo 1 dejó pendiente a propósito: ¿qué tiene que existir, en el código mismo, para que esa separación sea real y no solo un concepto en una pizarra? La respuesta tiene una condición muy concreta, y esta lección la demuestra con dos versiones de la misma función, ejecutadas una al lado de la otra.

Conexión con el módulo. Esta es la primera lección de tema del módulo, y construye la base sobre la que se apoyan las cinco que siguen: la lección 3 formaliza el objeto que representa un flag real, la lección 4 le agrega un porcentaje de exposición, la lección 5 le agrega un apagado de emergencia. Ninguna de esas piezas tiene sentido si primero no queda claro, como hoy, por qué un feature flag logra algo que un if cualquiera no logra.

Una analogía: el interruptor de pared, contra el cable pelado

Imagina dos formas de controlar la luz de una habitación. La primera: dos cables pelados que hay que tocar entre sí para que pase la corriente — funciona, técnicamente enciende la luz, pero para cambiar de "prendido" a "apagado" alguien tiene que meter la mano, cortar la conexión, y volver a armarla. La segunda: un interruptor de pared, instalado una sola vez, que cualquier persona puede mover con un dedo, sin herramientas, sin tocar ni un cable.

Las dos formas "controlan la luz". Pero solo una de las dos separa de verdad la instalación (el cableado, hecho una vez) de la operación diaria (prender y apagar, cuantas veces haga falta). Un if con un valor escrito directamente en el código es el cable pelado: funciona, pero cambiarlo exige meter la mano en el código y volver a armarlo —es decir, un nuevo deploy—. Un feature flag es el interruptor: la instalación (el código que lee el flag) se hace una vez, y a partir de ahí, cambiar de "prendido" a "apagado" no exige tocar el cableado nunca más.

Ejemplo trabajado: la misma feature, con y sin flag

Vamos a escribir dos versiones de la función que decide si la página de producto de Mercado muestra el carrusel de recommendations. La primera tiene el interruptor escrito adentro del código. La segunda lo recibe como dato externo:

// Version SIN feature flag: el interruptor esta escrito DENTRO del codigo.
// Cambiar este comportamiento exige editar esta funcion y volver a deployarla.
function renderProductPageHardcoded() {
  const SHOW_RECOMMENDATIONS = false; // para cambiar esto, hay que tocar el codigo y deployar de nuevo
  return SHOW_RECOMMENDATIONS ? 'muestra el carrusel de recomendaciones' : 'no muestra el carrusel';
}

// Version CON feature flag: el interruptor vive AFUERA del codigo, en un objeto
// de configuracion que se puede cambiar sin tocar ni volver a deployar esta funcion.
function renderProductPage(flag) {
  return flag.enabled ? 'muestra el carrusel de recomendaciones' : 'no muestra el carrusel';
}

console.log('=== Sin flag: el interruptor esta escrito en el codigo ===');
console.log(renderProductPageHardcoded());

console.log('\n=== Con flag: la MISMA funcion deployada, dos configs distintas ===');
const flagOff = { name: 'recommendations', enabled: false };
const flagOn = { name: 'recommendations', enabled: true };
console.log('flag.enabled=false -> ' + renderProductPage(flagOff));
console.log('flag.enabled=true  -> ' + renderProductPage(flagOn));
console.log('\nnota: renderProductPage() no cambio ni una linea entre las dos llamadas -- solo cambio el dato que le pasamos.');

Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:

=== Sin flag: el interruptor esta escrito en el codigo ===
no muestra el carrusel

=== Con flag: la MISMA funcion deployada, dos configs distintas ===
flag.enabled=false -> no muestra el carrusel
flag.enabled=true  -> muestra el carrusel de recomendaciones

nota: renderProductPage() no cambio ni una linea entre las dos llamadas -- solo cambio el dato que le pasamos.

Fíjate en lo que separa a las dos versiones, y en lo que tienen en común. Las dos, hoy, están deployadas — las dos funciones existen, corriendo, en el mismo archivo. La diferencia aparece cuando alguien quiere cambiar el comportamiento: para renderProductPageHardcoded(), la única forma de pasar de "no muestra" a "muestra" es editar la constante SHOW_RECOMMENDATIONS dentro del código y deployar esa edición. Para renderProductPage(), pasar de una respuesta a la otra no tocó absolutamente nada del cuerpo de la función — solo cambió el objeto flag que se le pasó como argumento. Esa es, en una sola comparación ejecutada, la diferencia completa entre "un if cualquiera" y "un feature flag": no es que uno tenga una condición y el otro no —los dos la tienen—, es dónde vive el valor que decide esa condición.

Por qué "afuera del código" es la pieza que importa

En el ejemplo, el objeto flag vive dentro del mismo archivo, como una constante más — eso es una simplificación pedagógica para que el ejemplo corra en Node sin dependencias externas. En un sistema real, ese objeto vive en un lugar que el código deployado puede leer en cada request, sin haber sido compilado junto con él: un archivo de configuración que se recarga, una fila en una base de datos, o un servicio dedicado (como LaunchDarkly, que vas a ver mencionado en los recursos de esta lección). La propiedad clave, sin importar dónde viva exactamente, es siempre la misma: el valor del flag puede cambiar sin que el código que lo lee vuelva a compilarse ni a deployarse. Eso es lo que hace que enabled: false de hoy pueda convertirse en enabled: true de mañana con un cambio de configuración — el tema completo de la lección 3, donde vas a ver la forma concreta que toma ese "lugar externo" en la práctica.

Vale la pena notar también lo que este ejemplo no resuelve todavía. flagOn y flagOff son dos objetos separados, escritos a mano, para mostrar el contraste — en un sistema real hay un único flag cuyo valor cambia con el tiempo, no dos objetos distintos. Y la decisión hoy es binaria (todos ven la feature o nadie la ve) — decidir que un porcentaje de compradores la vea es, específicamente, el trabajo de la lección 4.

Errores comunes

Acoplar deploy y release: tratar "el código está listo" como sinónimo de "ya se puede ver". Qué pasa: un equipo termina de programar recommendations, hace el deploy, y ese mismo deploy expone la feature al 100% de los usuarios de inmediato, porque nunca existió un interruptor intermedio. Por qué pasa: sin un feature flag, no hay ningún paso natural entre "el código compila y pasa las pruebas" y "todos lo ven" — el deploy mismo es el único evento que existe, así que termina cargando las dos responsabilidades a la vez. Cómo detectarlo: si la pregunta "¿podemos deployar esto hoy y decidir mañana si se muestra?" no tiene una respuesta clara, es señal de que deploy y release siguen acoplados. Cómo corregirlo: el ejemplo de esta lección es exactamente la corrección — envolver el comportamiento nuevo en un flag antes de deployarlo, para que el deploy y la decisión de exposición queden separados desde el primer día, sin importar qué porcentaje se elija después.

Confundir una variable de entorno fija por ambiente con un feature flag de verdad. Qué pasa: alguien usa una variable de entorno (ENABLE_RECOMMENDATIONS=true en producción, false en staging) y la llama "feature flag", pero cambiar su valor en producción exige reiniciar el servicio —y a veces un nuevo deploy del contenedor con la variable actualizada—. Por qué pasa: una variable de entorno también vive "afuera del código fuente", así que se siente parecida a un flag externo, aunque en la práctica requiera un ciclo de despliegue de infraestructura para cambiar. Cómo detectarlo: la pregunta correctora sigue siendo la misma — "¿puedo cambiar esto sin ningún tipo de despliegue, ni de código ni de infraestructura?" Si la respuesta es "solo con un reinicio o redeploy del servicio", no es un feature flag en el sentido de esta lección, aunque se parezca. Cómo corregirlo: un feature flag real se lee en tiempo de ejecución, en cada request o con una recarga muy frecuente — no solo al arrancar el proceso.

Pensar que la sola existencia del flag ya resuelve el lanzamiento de recommendations. Qué pasa: el equipo felicita el trabajo de envolver la feature en un if (flag.enabled) y da por cerrado el módulo, sin haber decidido todavía qué porcentaje de usuarios debería ver la feature, ni cómo se apaga de emergencia. Por qué pasa: el mecanismo (el if que lee el flag) es la parte más visible y la más fácil de verificar con una prueba rápida, así que se siente como "el trabajo". Cómo detectarlo: si la única pregunta que el equipo puede contestar sobre el flag es "¿está prendido o apagado?" —y no "¿a qué porcentaje?", "¿tiene kill switch probado?", "¿de qué tipo es y cuándo se retira?"—, todavía falta la mayor parte de este módulo. Cómo corregirlo: trata esta lección como el cimiento, no como la casa completa — las lecciones 3 a 7 son las que convierten "existe un flag" en "el flag está listo para un lanzamiento real".

Ejercicios

Ejercicio 1 — Encuentra el flag real. De las siguientes dos implementaciones, ¿cuál es un feature flag según el criterio de esta lección, y cuál no? Justifica con una frase cada una.

  • (a) if (user.email.endsWith('@mercado.com')) { showRecommendations(); } — visible solo para empleados, escrito directamente en el código.
  • (b) if (flags.get('recommendations').enabled) { showRecommendations(); }, donde flags se lee desde un servicio de configuración externo en cada request.
Ver solución

(b) es el feature flag real: el valor de enabled vive afuera del código, en un servicio externo, y se puede cambiar sin volver a deployar la función que lo lee. (a) no lo es, aunque también sea una condición: user.email.endsWith('@mercado.com') es una regla escrita directamente en el código —cambiar a quién le aplica (por ejemplo, agregar @otraempresa.com) exige editar esa línea y deployarla de nuevo—. La similitud engañosa es que las dos "deciden algo condicionalmente"; la diferencia que importa es dónde vive el valor que se está evaluando.

Ejercicio 2 — Rediseña el ejemplo (a). Usando el patrón de renderProductPage(flag) de esta lección, reescribe la condición del ejercicio 1(a) para que sea un feature flag de verdad. No hace falta lógica de porcentaje todavía — solo separa el valor que decide del código que lo usa.

Ver solución
function showRecommendationsToEmployees(flag) {
  return flag.enabled ? 'muestra el carrusel (empleados)' : 'no muestra el carrusel';
}

const employeePreviewFlag = { name: 'recommendationsEmployeePreview', enabled: true };
console.log(showRecommendationsToEmployees(employeePreviewFlag));

El cambio central: la función ya no contiene ningún valor fijo que decida el comportamiento — solo lee flag.enabled. Activar o desactivar el preview para empleados ahora es cuestión de cambiar el objeto employeePreviewFlag (en un sistema real, en el lugar externo donde vive), sin tocar showRecommendationsToEmployees() nunca más.

Ejercicio 3 — Explica sin usar la palabra "deploy". En dos o tres frases, explica a alguien de negocio (que no programa) qué gana Mercado al usar un feature flag para recommendations, sin usar la palabra "deploy" ni "código". Puedes usar la analogía del interruptor.

Ver solución

Un ejemplo de respuesta: "Es como tener la instalación eléctrica de un cuarto nueva terminada, pero con un interruptor en la pared en vez de cables sueltos: el trabajo de construcción ya está listo, y prender o apagar la luz —mostrar o no recommendations— se convierte en algo que se puede decidir el día que queramos, sin tener que llamar de nuevo a los electricistas cada vez que cambiamos de opinión." La idea central que debe aparecer, sin el vocabulario técnico: el trabajo pesado (construir la feature) queda separado de la decisión de negocio (cuándo mostrarla), y la segunda se puede tomar —y revertir— con mucha más rapidez que la primera.

Resumen y siguiente paso

En esta lección ejecutaste, lado a lado, la diferencia real entre un if común y un feature flag: no es que uno tenga una condición y el otro no, es que el valor que decide esa condición vive afuera del código en el caso del flag, y adentro en el caso del if común. Esa diferencia —cambiar el comportamiento sin volver a deployar— es, literalmente, el mecanismo que hace posible en código la separación entre deploy y release que nombró el módulo 1.

Antes de avanzar deberías poder: distinguir un feature flag real de una condicional común, dado un fragmento de código; explicar por qué "dónde vive el valor" importa más que "si hay una condición"; y anticipar que un flag real necesita un lugar externo donde vivir, más allá del archivo de código mismo.

La lección 3 construye exactamente ese lugar externo: el objeto completo que representa un feature flag real —con nombre, descripción, dueño, y los campos que las lecciones 4 y 5 van a usar— y un pequeño registro donde varios flags conviven, tal como lo harían en un sistema de producción.

Recursos

  • Pete Hodgson (con Martin Fowler), "Feature Toggles (aka Feature Flags)" — martinfowler.com/articles/feature-toggles.html. La sección "Implementation Techniques" describe justamente esta distinción entre esconder un if en el código y desacoplar la decisión en una configuración externa. En inglés.
  • Charity Majors, "Deploys Are the WRONG Way to Change User Experience" — honeycomb.io/blog/deploys-wrong-way-change-user-experience. El argumento de fondo de por qué separar deploy de release —con feature flags como mecanismo concreto— cambia por completo cómo un equipo puede lanzar cambios. En inglés.