En el post anterior te conté cómo gracias a la implementación de Feature Flags podemos agilizar nuestro desarrollo y realizar despliegues incrementales.

En esta oportunidad iremos un poco más profundo y veremos cómo podemos podemos activar o desactivar funcionalidad para ciertos rangos de tiempos o para un porcentaje determinado de usuarios.

Contexto

Si bien en muchos casos activar o desactivar una funcionalidad es suficiente, hay ciertos escenarios donde queremos tener un poco más de control sobre qué condiciones activan o desactivan determinado aspecto del sistema.

En ciertos escenarios, esas condiciones pueden ir variando con el tiempo, y por supuesto no queremos estar reconfigurando nuestras aplicaciones cada vez que alguna de las condiciones cambia.

Requerimientos

Los requerimientos que vamos a intentar satisfacer son los siguientes:

  1. Activar o desactivar funcionalidades en momentos específicos.
  2. Activar o desactivar funcionalidades para un determinado porcentaje de peticiones.

Activar o desactivar funcionalidades en momentos específicos

Un claro ejemplo de esta necesidad es habilitar alguna funcionalidad durante fechas particulares, como es el caso de algún descuento especial por Navidad o Black Friday.

Podríamos tener un caso en el cuál para Navidad deseamos habilitar una página especial con todos los productos que tienen un descuento especial del 25%, de forma tal que durante los días 24 y 25 de diciembre suceda lo siguiente:

  1. Mostremos una nueva entrada del menú.
  2. Este disponible una pagina especial con la lista de productos
  3. Aplicar un descuento del 25% a esos productos durante ese lapso de tiempo.

TimeWindowFilter

Para nuestro agrado, ya disponemos de un filtro llamado TimeWindowFilter que nos permite establecer fechas desde y hasta.

Lo primero que debemos hacer es agregar el filtro como parte de la configuración de servicios en Startup.ConfigureServices utilizando el método de extensión AddFeatureFilter<>():

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // More services
        services.AddFeatureManagement()
            .AddFeatureFilter<TimeWindowFilter>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // ...
    }
}

Luego, tendremos que generar una nueva configuración en el archivo appsettings.json al cual llamaremos OfertasNavidenias:

// appsettings.json
"FeatureManagement": {
  "OtroFeatureFlag": true,
  "OfertasNavidenias": {
    "EnabledFor": [
      {
        "Name": "Microsoft.TimeWindow",
        "Parameters": {
          "Start": "2021-12-24T00:00:00",
          "End": "2021-12-25T23:59:59"
        }
      }
    ]
  }
}

En este caso estamos indicando que el Feature Flag OfertasNavidenias se activa o desactiva para la lista de filtros incluida como parte del arreglo. En este caso estamos usando el filtro por defecto que nos provee el framework, Microsoft.TimeWindow. Los valores Start y End son las fechas/horas durante las cuales la funcionalidad está activa.

Ya con la configuración en su lugar, modificaremos la vista _Layout.cshtml para incluir la nueva entrada del menú solo cuando la funcionalidad "OfertasNavidenias" se encuentre habilitada. Para esto usamos el Tag Helper <feature>.

<header>
    <nav class="navbar">
        <ul class="navbar-nav">
            @* More menues... *@
            <feature name="OfertasNavidenias">
                <li class="nav-item">
                    <a asp-controller="OfertasNavidenias" asp-action="Index">Ofertas navideñas</a>
                </li>
            </feature>
        </ul>
    </nav>
</header>

Por cuestiones internas del framework, tuve que reemplazar la ñ cuando establecí el nombre del Feature Flag. Eso explica el error tipográfico en OfertasNavidenias.

Por el lado del controlador, necesitamos evitar que la acción del controlador Index sea accesible si la funcionalidad "OfertasNavidenias" para lo cual usamos el atributo [FeatureGate("OfertasNavidenias")].

Por el otro lado, necesitamos aplicar un descuento especial a cada producto. Para eso tenemos disponible el servicio IFeatureManager, el cual nos permite determinar si la funcionalidad se encuentra activa.

public class OfertasNavideniasController : Controller
{
    private readonly IFeatureManager _featureManager;

    public OfertasNavideniasController(IFeatureManager featureManager) =>
        _featureManager = featureManager;

    [FeatureGate("OfertasNavidenias")]
    public async Task<ActionResult> Index()
    {
        var isOfertasNavideniasEnabled = await _featureManager.IsEnabledAsync("OfertasNavidenias");

        if (isOfertasNavideniasEnabled)
        {
            var productosConDescuento = productos
                .Select(producto =>
                {
                    producto.Precio = producto.Precio * 0.75;

                    return producto;
                })
                .ToList();

            return View(productosConDescuento);
        }

        return View(productos);
    }
}

Pruebas

La forma de verificar o validar que nuestro Feature Flag junto con su configuración está funcionando correctamente es, como siempre, ejecutando la aplicación. Si nos encontramos dentro del rango de fechas indicadas entonces la entrada del menú estará disponible.

Activar o desactivar funcionalidades para un determinado porcentaje de peticiones

Cuando una nueva funcionalidad está lista para ser puesta en producción, es habitual activar dicha funcionalidad para un grupo reducido de usuarios, de forma que ante un eventual fallo la cantidad de usuarios afectados sea reducida.

Por ejemplo, podríamos habilitar una funcionalidad para el 5% de los usuarios de nuestro sitio web.

PercentageFilter

El framework ya nos provee un filtro llamado PercentageFilter que nos permite configurar el porcentaje de peticiones para los cuales una funcionalidad estará habilitada.

Para esto tenemos que agregar este nuevo filtro en Startup.ConfigureServices y agregar la sección correspondiente en appsettings.json.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // More services
        services.AddFeatureManagement()
            .AddFeatureFilter<TimeWindowFilter>()
            .AddFeatureFilter<PercentageFilter>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // ...
    }
}
// appsettings.json
"FeatureManagement": {
  "OtroFeatureFlag": true,
  "OfertasNavidenias": {...},
  "FuncionalidadBeta": {
    "EnabledFor": [
      {
        "Name": "Percentage",
        "Parameters": {
          "Value": 5
        }
      }
    ]
  }
}

En este caso la propiedad Value de la configuración corresponde al porcentaje de peticiones para los cuales esta funcionalidad estará habilitada.

Por último y como hicimos anteriormente, modificaremos la vista _Layout.cshtml para incluir la nueva entrada del menú "Beta":

<header>
    <nav class="navbar">
        <ul class="navbar-nav">
            @* More menues... *@
            <feature name="FuncionalidadBeta">
                <li class="nav-item">
                    <a asp-controller="FuncionalidadBeta" asp-action="Index">Funcionalidad Beta</a>
                </li>
            </feature>
        </ul>
    </nav>
</header>

Pruebas

Al ejecutar la aplicación, deberíamos notar que solo 5% de las veces que navegamos hacia el Home veremos disponible la entrada de menú Funcionalidad Beta.

Limitaciones

Esta implementación tiene como limitación que solo funciona por petición. Esto crea escenarios donde un usuario ve un resultado diferente en forma aleatoria, incluso si refresca el navegador.

Idealmente, cuando un usuario accede a la plataforma y la funcionalidad se encuentra activa, esta funcionalidad debería mantenerse activa durante el tiempo que dure la sesión del usuario.

En algunos casos, incluso queremos que la funcionalidad mantenga el mismo estado en sesiones diferentes.

Para resolver este inconveniente utilizaremos un servicio llamado ISessionManager o utilizando filtros personalizados. En futuros post veremos en detalle cómo implementar dichos conceptos.

Conclusión

Hoy profundizamos un poco más sobre Feature Flags y vimos cómo podemos utilizar algunos filtros condicionales provistos por el framework. Vimos como ejemplo cómo activar una funcionalidad para un porcentaje de peticiones y como activar una funcionalidad en cierto rango de fechas.

Todavía nos faltan muchas cosas, como manejo de funcionalidades desactivadas, manejo de sesión, filtros personalizados, etc. De a poco iremos profundizando y descubriendo todo el potencial que los Feature Flags nos ofrecen.