Hablemos sobre .Net y Azure

Etiqueta: Feature Flags

Feature Flags personalizados

Hola a todos! Ya llevamos una serie de artículos dedicados a la implementación de Feature Flags. Entre otras cosas, vimos como utilizar algunos filtros condicionales que el framework nos provee y cómo aplicar middlewares y filtros MVC.

En el post de hoy veremos uno de los requerimientos más utilizados y sin los cuales los Feature Flags no serían tan poderosos: Feature Flags personalizados.

Contexto

Cuando analizamos los Feature Flags condicionales vimos como podemos aplicar algún tipo de lógica, que puede ser una ventana de tiempo (TimeWindowFilter) o un porcentaje de peticiones (PercentageFilter), para activar o desactivar funcionalidades. Claro quu si bien agregan valor, no son suficientes.

En ocasiones necesitamos tener un mayor control sobre como determinar si una funcionalidad se encuentra activa o no, incluso podemos necesitar consultar servicios externos o una base de datos.

Requerimiento

Como parte del desarrollo de nuestra estrategia de expansión de nuestro negocio eCommerce, queremos ofrecer una lista de productos especialmente seleccionado con un descuento especial promocional. Este descuento estará disponible solo los días viernes.

Implementación

Armar nuestro propio filtro requiere implementar la interface IFeatureFilter, la cuál define solo el método EvaluateAsync. Aquí podremos escribir nuestra lógica de negocio y retornar un valor booleano indicando si la funcionalidad se encuentra activa o no:

public interface IFeatureFilter : IFeatureFilterMetadata
{
    Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context);
}

Los pasos necesarios para completar nuestro filtro son:

  1. Implementar la interface IFeatureFilter
  2. Registrar el filtro
  3. Agregar la configuración
  4. Modificar la vista

IFeatureFilter

La idea de nuestro filtro es retornar true si el día de la semana es viernes, de lo contrario retornar false. Para hacerlo un poco más flexible, vamos a parametrizar el día de la semana de forma que podamos utilizarlo para otras promociones.

Crearemos una clase WeeklyOffersFeatureFilter e implementaremos la interface IFatureFilter. El código debería verse mas o menos así:

[FilterAlias("WeeklyOffersFilter")]
public class WeeklyOffersFeatureFilter : IFeatureFilter
{
    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
    {
        var dayOfWeek = context.Parameters.GetValue<string>("DayOfWeek");
        var result = DateTime.Now.DayOfWeek.ToString() == dayOfWeek;

        return Task.FromResult(result);
    }
}

Analicemos los aspectos importantes de nuestro filtro:

  • [FilterAlias("WeeklyOffersFilter")]:
    Este atributo es necesario para indicarle al framework el nombre del filtro a utilizar. Es decir, “para el Feature Flag WeeklyOffers utilizar el filtro cuyo alias es WeeklyOffersFilter. Tiene que ser el mismo que utilizaremos en la configuración en el archivo appsettings.json.
  • context.Parameters.GetValue<string>("DayOfWeek"):
    Mediante el contexto podemos leer los parámetros establecidos en la configuración. En este caso estamos obteniendo el parámetro "DayOfWeek".
  • result:
    Simplemente comparamos el día de la semana actual contra el parámetro.

Registrar el filtro

Como siempre, solo tenemos que registrarlo utilizando el método de extensión AddFeatureFilter<>() en la clase Startup:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...

        services.AddFeatureManagement()
            .AddFeatureFilter<WeeklyOffersFeatureFilter>();
    }

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

Configuración

Finalmente necesitamos generar una nueva configuración en el archivo appsettings.json:

{
  // ...
  "FeatureManagement": {
    "WeeklyOffers": {
      "EnabledFor": [
        {
          "Name": "WeeklyOffersFilter",
          "Parameters": {
            "DayOfWeek": "Friday"
          }
        }
      ]
    }
  }
}

Lo mas importante aqui es la linea 7, donde indicamos el nombre del filtro a utilizar, que tiene coincidir con el valor del atributo [FilterAlias("WeeklyOffersFilter")] que utilizamos cuando implementamos la interface.

Vista

El único paso que nos queda pendiente es modificar la vista, esto ya lo hemos hecho en post anteriores. En nuestro caso modificamos la vista del menú principal (_Layout.cshtml) para incluir una nueva entrada cuando nuestro Feature Flag WeeklyOffers se encuentre activa.

<feature name="WeeklyOffers">
  <li class="nav-item">
    <a asp-controller="Home" asp-action="NewFeature">
        Ofertas semanales
    </a>
  </li>
</feature>

Conclusión

Poder implementar filtros personalizados nos da mucha flexibilidad a la hora de habilitar funcionalidades. Por ejemplo, si nuestra funcionalidad depende de otro servicio que puede no estar funcionando o no estar activo aún, podríamos ejecutar un recuest a dicho servicio y devolver true si y solo si el request es exitoso.

En el próximo post veremos como combinar Feature Flags con Azure App Configuration para guardar nuestras configuraciones de forma centralizada y segura.

Feature Flags: Middlewares y filtros MVC

De a poco estamos descubriendo todo el potencial que los Feature Flags tienen parea ofrecernos, aprendimos los conceptos principales, como usar Feature Flags condiciones y como manejar Features Flags inactivas.

En esta ocasión veremos como aplicar aplicar filtros MVC y middlewares solo si determinada funcionalidad se encuentra activa.

Filtros MVC

Los filtros MVC nos permiten agregar funcionalidad en forma muy versátil y dinámica como parte del procesamiento de una petición o request. Una de las principales características es que podemos agregar funcionalidad sin necesariamente modificar un controlador o acción.

Los Feature Flags nos van a permitir aplicar filtros MVC solo si una funcionalidad determinada se encuentra activa.

Requerimiento

Tenemos un filtro MVC AddBetaHeaderFilter que agrega una cabecera especial a la petición indicando que la versión en ejecución es "Beta". De esta forma otros componentes pueden realizar modificaciones a la petición según la versión.

public class AddBetaHeaderFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, 
        ActionExecutionDelegate next)
    {
        context.HttpContext
            .Request
            .Headers
            .Add("X-Version", "Beta");

        await next();
    }
}

Necesitamos aplicar este filtro solo y solo si el Feature Flag "Beta" se encuentra activo.

Implementación

Por suerte para nosotros solo necesitamos agregar el filtro a los filtros generales de MVC utilizando el helper AddForFeature<>. Para esto modificaremos la llamada a services.AddControllersWithViews() del método ConfigureServices de la clase Startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
        options.Filters.AddForFeature<AddBetaHeaderFilter>("Beta"));

    services.AddFeatureManagement();
    // More services...
}

Middlewares

Los middlewares son muy similares a los filtros MVC ya que nos permiten manipular las peticiones y las respuestas. A diferencia de los filtros, estos no nos brindan acceso al contexto de MVC.

También podemos aplicar middlewares al pipeline utilizando Feature Flags.

Requerimiento

Continuando con el ejemplo anterior, tenemos un middleware AddBetaHeaderMiddleware que agrega la misma cabecera “Beta” que agregamos con el filtro MVC de la sección anterior.

public class AddBetaHeaderMiddleware
{
    private readonly RequestDelegate _next;

    public AddBetaHeaderMiddleware(RequestDelegate next) =>
        _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        context
            .Request
            .Headers
            .Add("X-Version", "Beta");

        await _next(context);
    }
}

Necesitamos aplicar este middleware solo y solo si el Feature Flag "Beta" se encuentra activo.

Implementación

Para aplicar este filtro, tendremos que modificar el método Configure de la clase Startup, donde utilizaremos el método de extensión ya provisto por el framework UseMiddlewareForFeature<>().

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

    app.UseMiddlewareForFeature<AddBetaHeaderMiddleware>("Beta");

    app.UseEndpoints(endpoints => {/**/});
}

El orden de los factores sí altera el producto

Tener en cuenta que los middlewares se ejecutan en el orden en el que se agregan. De modo tal, si en el ejemplo anterior registramos el middleware después de app.UseEndpoints(), el Feature Flag no funcionaría.

Conclusión

Paso a paso vamos descubriendo todo el potencial de los Feature Flags y como utilizarlos para lograr un desarrollo realmente ágil e incremental. En este post aplicamos middlewares y filtros.

En el próximo post iremos un poquito mas a fondo e implementaremos un Feature Flag personalizado.

Manejo de Features Flags inactivas

Como vimos en el post introductorio, cuando intentamos navegar hacia una página que no esta activa debido a un Feature Flag desactivado recibimos un mensaje de error 404 - Not Found o Página no encontrada.

En algunos escenarios ese error puede ser suficiente, pero en determinadas ocasiones, redireccionar al usuario a otra pagina o devolver algún otro código de error puede resultar una mejor opción.

Requerimiento

Nuestro sitio web tiene una vista con todos los productos que tienen un descuento especial por Navidad. Utilizamos Feature Flags condicionales para garantizar que esta funcionalidad este activa solo los días 24 y 25 de Diciembre.

Cuando un usuario accede a esta lista especial de descuentos fuera del periodo navideño, queremos redireccionar al usuario a otra vista para informarle que los descuentos ya no se encuentran disponibles e invitarlo a revisar otros posibles descuentos.

IDisabledFeaturesHandler

Para poder manejar en forma personalizada cómo respondemos ante una funcionalidad inactiva, tenemos la opción de implementar al interface IDisabledFeaturesHandler:

public interface IDisabledFeaturesHandler
{
    Task HandleDisabledFeatures(
        IEnumerable<string> features,
        ActionExecutingContext context);
}

Como podemos observar, la interfaz tiene solo un método con la lista de Feature Flags que se requirieron y el contexto de MVC actual. Veamos en detalle que significa cada uno de esos parámetros.

IEnumerable<string> features

El primer parámetro contiene la lista de todos los Feature Flags que fueron requeridos para poder acceder a la funcionalidad en cuestión. Alguno o algunos de ellos se encuentran inactivos y dispararon el manejador.

Recordemos que cuando una funcioalidad puede dependender de más de un Feature Flag, y además podemos requerir que todos los Feature Flags esten activos o al menos uno. En los siguiente ejemplo lo vemos en forma más detallada:

public class MyController : Controller
{
    // Cualquiera de los features es suficiente
    [FeatureGate(
        RequirementType.Any, 
        "FeatureFlag1", 
        "FeatureFlag2")]
    public IActionResult Funcionalidad1() => 
        View();

    // Ambos features deben estar activos
    [FeatureGate(
        RequirementType.All, 
        "FeatureFlag1", 
        "FeatureFlag2")]
    public IActionResult Funcionalidad2() => 
        View();
}

ActionExecutingContext context

Este parámetro corresponde al contexto de ejecución de la acción dentro de MVC (el mismo que tenemos disponibles en los Action Filters!!!) y nos brinda por un lado información sobre el controlador y acción que se intentaron ejecutar, por el otro lado nos permite manejar la respuesta que enviaremos al cliente.

Manos a la obra

Para completar nuestro requerimiento vamos a generar una nueva clase, la cual llamaremos DisabledFeaturesHandler, e implementaremos la interfaz IDisabledFeaturesHandler.

En el método HandleDisabledFeatures redireccionaremos al usuario a una vista llamada OtherDiscounts.cshtml donde le notificaremos que los descuentos ya no están disponibles.

public class DisabledFeatureHandler : IDisabledFeaturesHandler
{
    public Task HandleDisabledFeatures(
        IEnumerable<string> features,
        ActionExecutingContext context)
    {
        var viewResult = new ViewResult
        {
            ViewName = "Views/Home/OtherDiscounts.cshtml"
        };

        context.Result = viewResult;

        return Task.CompletedTask;
    }
}

Por último, tenemos que incluir este manejador como parte de los servicios responsables del manejo de Features Flags. Esto lo haremos en ConfigureServices de la clase Startup:

public class Startup
{
    public void ConfigureServices(
        IServiceCollection services)
    {
        // ...
        services.AddFeatureManagement()
            .UseDisabledFeaturesHandler(
                new DisabledFeaturesHandler());
    }

    public void Configure(
        IApplicationBuilder app)
    {
        // ...
    }
}

A partir de este momento, cada vez que el usuario se dirija a la vista de descuentos navideños fuera del periodo correspondiente, será automáticamente redirigido a una vista particular en lugar de recibir un error 404 - NotFound:

Manejador de Feature Flags inactivos.

Conclusión

En este post vimos como con muy poquito codigo podemos manejar de forma elegante y simple como reaccionar en forma particular a alguna funcionalidad que se encuentra deshabilitada y tenemos la necesidad de responder de una manera diferente.

Todavía nos queda mucho por descubrir sobre manejo de sesión, implementación de filtros personalizados, middlewares y much más.

Feature Flags condicionales

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.

Desarrollo incremental y ágil con Features Flags

Hola a todos! Voy a comenzar el post con una pregunta: ¿Has tenido que revertir uno o más commits en producción por una nueva funcionalidad que trajo problemas?

Si la respuesta es No, siento un poco de envidia! Pero si tu respuesta es Si (como es mi caso), este post es para vos. Vamos a ver cómo podemos resolver ese problema utilizando Feature Flags.

Contexto

Seguramente has escuchado hablar alguna vez de la idea de desplegar código frecuentemente y en forma incremental, iteraciones cortas con retroalimentación (feedback) inmediato, fallar rápido (fail fast), etc.

Seguramente cuando tuviste que revertir ese commit conflictivo te hubiera gustado poder “apagar” esa funcionalidad sin revertir commits.

Quizás te gustaría desplegar una nueva funcionalidad pero no activarla, o activarla para un grupo reducido de usuarios.

Todas las respuestas conducen a Feature Flags!

Feature Flags

Feature Flags es una práctica que nos permite controlar total o parcialmente como se comporta una funcionalidad o grupo de funcionalidades mediante configuración de una forma estándar y simple.

Dicho de otro modo, los Features Flags nos van a permitir “encender” o “apagar” funcionalidades mediante configuración.

Antes de comenzar

Antes de comenzar a escribir código, veamos un poco como luce la solución actual., donde tenemos una aplicación Web MVC muy simple con la siguientes 3 características:

  1. Una entrada en el menú principal llamada "Nueva Funcionalidad".
Home
Home
<header>
    <nav class="...">
        <ul class="...">
            <li class="nav-item">
                <a asp-controller="Home" asp-action="Index">Home</a>
            </li>
            <li class="nav-item">
                <a asp-controller="Home" asp-action="Privacy">Privacy</a>
            </li>
            <li class="nav-item">
                <a asp-controller="Home" asp-action="NuevaFuncionalidad">Nueva Funcionalidad</a>
            </li>
        </ul>
    </nav>
</header>
  1. Una acción llamada NuevaFuncionalidad en el controlador Home que nos dirige una vista.
public class HomeController : Controller
{
    public IActionResult Index() => View();

    public IActionResult Privacy() => View();

    public IActionResult NuevaFuncionalidad() =>
        View();
}
  1. Una vista llamada NuevaFuncionalidad.
@{
    ViewData["Title"] = "Nueva Funcionalidad";
}

<div class="text-center">
    <h1 class="display-4">Nueva Funcionalidad</h1>
</div>

Requerimientos

¿Qué pasaría si esta nueva funcionalidad tiene una falla y necesitamos desactivarla? ¿Cómo podemos incorporar esta nueva funcionalidad si todavía esta incompleta?

El requerimiento que vamos a atacar es simple: necesitamos poder desactivar o activar esta nueva funcionalidad en forma simple y rápida, sin necesidad de crear nuevos commits y de una forma estandarizada.

Manos a la obra

Para poder comenzar a utilizar Features Flags vamos a necesitar instalar una librería llamada Microsoft.FeatureManagement.AspNetCore.

dotnet add package Microsoft.FeatureManagement.AspNetCore

Luego tendremos que implementar 3 aspectos:

  1. Configuración e inyección de servicios
  2. Vistas
  3. Controladores

Configuración e inyección de servicios

Comenzaremos por configurar los servicios necesarios mediante Inyección de Dependencias.

// ...
using Microsoft.FeatureManagement;

namespace FacuTheRock.FeaturesFlags.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // ...
            services.AddFeatureManagement();
        }

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

Una vez configurados los servicios, debemos proceder con las configuraciones necesarias. Por suerte para nosotros, solo necesitamos agregar una sección llamada FeatureManagement dentro del archivo appsettings.json.

En esta sección listaremos todas las funcionalidades e indicaremos si esta activa o inactiva. Agregaremos una nueva funcionalidad a la lista a la cual llamaremos NuevaFuncionalidad cuyo valor dejaremos en false o inactivo.

{
  // ...
  "FeatureManagement": {
    "NuevaFuncionalidad": false,
    "Funcionalidad1": true,
    // ...
  }
}

Vistas

Ahora necesitamos mostrar y ocultar la opción del menú Nueva Funcionalidad en función del Feature Flag que configuramos anteriormente. Para eso tenemos disponible un tag helper llamado feature al cual le indicaremos el nombre del Feature Flag que queremos observar.

Es importante incluir en el código de la vista la referencia a Microsoft.FeatureManagement.AspNetCore:

@*_Layout.cshtml*@

@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

<header>
    <nav class="...">
        <ul class="...">
            <li class="nav-item">
                <a asp-controller="Home" asp-action="Index">Home</a>
            </li>
            <li class="nav-item">
                <a asp-controller="Home" asp-action="Privacy">Privacy</a>
            </li>
            <feature name="NuevaFuncionalidad">
                <li class="nav-item">
                    <a asp-controller="Home" asp-action="NuevaFuncionalidad">Nueva Funcionalidad</a>
                </li>
            </feature>
        </ul>
    </nav>
</header>

El tag helper se ocupa de verificar si la funcionalidad se encuentra activa, y de ser así renderiza los elementos contenidos dentro el.

En este caso y debido a la mediante configuramos indicamos que la funcionalidad se encuentra inactiva, la opción de menú correspondiente no estará disponible.

Controlador

Si bien ya no vemos la opción de menú disponible, todavía es posible acceder al controlador si utilizamos la URL directamente, por ejemplo http://localhost:5000/Home/NuevaFuncionalidad. En el caso de que el Feature Flag este desactivado, el controlador no debería ser accesible y debería retornar respuesta un error 404 - NotFound.

Por suerte para nosotros, solo basta con agregar el atributo FeatureGate con el nombre de la funcionalidad a verificar:

public class HomeController : Controller
{
    public IActionResult Index() => View();

    public IActionResult Privacy() => View();

    [FeatureGate("NuevaFuncionalidad")]
    public IActionResult NuevaFuncionalidad() =>
        View();
}

Testing

Lo único que nos queda por hacer es jugar con la configuración de nuestro Feature Flag y ver como impacta tanto a nivel de Vista como a nivel de controlador.

Menu - Feature Flag true
Menu – Feature Flag true
Menu - Feature Flag false
Menu – Feature Flag false
Controlador - Feature Flag true
Controlador – Feature Flag true
Controlador - Feature Flag false
Controlador – Feature Flag false

Conclusión

Hoy vimos como gracias a la implementación de Features Flags podemos no solo activar o desactivar funcionalidades sin escribir código adicional y solo mediante configuración, sino que además nos permite desplegar funcionalidades parciales y activarlas en el momento que consideremos conveniente.

Pero esto es solo el comienzo, en los próximos artículos veremos como podemos verificar sistemas externos o nuestra propia base de datos para validar los flags, como activar filtros MVC, middlewares y mucho mas.

© 2021 Facu The Rock

Tema por Anders NorenArriba ↑