前言

我们知道,目前大多数应用程序在正式发布到生产环境之前都会经历多个不同的测试环境,通过让应用程序在多个不同的环境中运行来及时发现并解决问题,避免在线上发生不必要的损失。这是对于整个软件的发布流程来讲。但是如果想让我们的应用程序在线上环境中通过满足一些动态条件(比如电商平台在某一时间段的促销活动)从而能开启一些临时功能的话又该怎么办呢?如果你试图通过重新打包发布的方式来解决这个问题,可能有些过于大动干戈了。本文,笔者将介绍通过 feature flag 的方式来解决这个问题。

正文

feature flag 中文可译为 功能开关。通过使用这种方式,可以对我们的功能进行条件化配置,当程序在线上环境运行时,如果当前环境符合我们某一特性功能开启/关闭的条件时,应用程序会自动开启/关闭该功能。整个过程不需要人工参与,全部都是由系统本身来完成相应功能的开启和关闭。

那在 .net core 中,我们该如何实现该功能呢?

微软为我们很贴心地提供了两个开发包:microsoft.featuremanagementmicrosoft.featuremanagement.aspnetcore,该实现是基于 .net core 的配置系统 ,所以任何 .net core 程序都可以轻易集成该功能。

目前还处于预览版阶段,需要在 nuget 上勾选 use prerelease

因此,我们只需将对应包安装到我们的应用程序中即可。

接下来,我们就一起看一下如何在 asp.net core 中集成该功能。

使用入门

创建一个 asp.net core web application 后,安装如下包:

install-package microsoft.featuremanagement.aspnetcore -version 2.0.0-preview-010610001-1263

接着在 startup 中的 configureservices 进行相应配置,示例如下:

public void configureservices(iservicecollection services)
{
    services.addfeaturemanagement();
    services.addcontrollerswithviews();
}

至此,我们的程序已经支持 feature flag 功能了,使用方式就简单了,这里展示一个相对简单的方式。

首先,在 appsettings.json 进行配置,如下所示:

  "featuremanagement": {
    "newfeatureflag": true,
  }

然后,在 index.cshtml 通过使用 feature 标签来进行相应配置,示例如下所示:

@using microsoft.featuremanagement
@inject ifeaturemanager featuremanager
@addtaghelper *,microsoft.featuremanagement.aspnetcore
@{
    viewdata["title"] = "home page";
}

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

    <feature name="newfeatureflag" requirement="all">
        <a asp-action="newfeature">go to the new feature.</a>
    </feature>
</div>

此时,我们运行起程序后就可以看到 feature 标签内的内容就可以渲染出来,如果我们在配置中将 newfeatureflag 值设置为 false 后,feature 标签内的内容就会消失,你可以通过查看网页源码的方式来查看具体细节。

接下来笔者介绍一下微软为我们内置的两个功能开关:

  • percentagefilter
  • timewindowfilter

percentagefilter

percentagefilter 是支持百分比的随机开关,通过使用这种方式,可以让一个功能在每次请求中以一个百分比概率的形式来开启/关闭。

  • 注入功能开关

startup.cs

public void configureservices(iservicecollection services)
{
    services.addfeaturemanagement()
        .addfeaturefilter<percentagefilter>();
    services.addcontrollerswithviews();
}
  • 配置功能开关

appsettings.json

  "featuremanagement": {
    "randomflag": {
      "enabledfor": [
        {
          "name": "percentage",
          "parameters": {
            "value": 50
          }
        }
      ]
    }
  }

这里配置的是在每次请求时以 50% 的概率来开启该功能,其对应的配置类为:percentagefiltersettings

  • 使用功能开关

index.cshtml

@using microsoft.featuremanagement
@inject ifeaturemanager featuremanager
@addtaghelper *,microsoft.featuremanagement.aspnetcore
@{
    viewdata["title"] = "home page";
}

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

    <feature name="randomflag">
        <h2>i am a random flag!</h2>
    </feature>
    
    <p>learn about <a href="https://docs.microsoft.com/aspnet/core">building web apps with asp.net core</a>.</p>
</div>

这时,当我们运行起程序后就会看到如下图所示的效果:

timewindowfilter

timewindowfilter 是时间段的随机开关,通过使用这种方式,可以让一个功能在指定的时间段内来开启/关闭。

  • 注入功能开关

startup.cs

public void configureservices(iservicecollection services)
{
    services.addfeaturemanagement()
        .addfeaturefilter<timewindowfilter>();
    services.addcontrollerswithviews();
}
  • 配置功能开关

appsettings.json

  "featuremanagement": {
    "randomflag": {
      "enabledfor": [
        {
          "name": "percentage",
          "parameters": {
            "start": "2019/12/27 5:04:00 +00:00",
            "end": "2019/12/27 5:04:05 +00:00"
          }
        }
      ]
    }
  }

这里需要注意的是,配置里面的 startenddatetimeoffset 类型,并且需要配置为 utc 的时间,所以在实际使用过程中需要考虑时区问题(你可以通过调用 datetimeoffset.utcnow 的方式来获取相应时间的格式)。其对应的配置类为:timewindowfiltersettings

  • 使用功能开关

index.cshtml

@using microsoft.featuremanagement
@inject ifeaturemanager featuremanager
@addtaghelper *,microsoft.featuremanagement.aspnetcore
@{
    viewdata["title"] = "home page";
}

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

    <feature name="timedflag">
        <h2>i am a timed flag!</h2>
    </feature>
    <p>@datetimeoffset.utcnow.tostring()</p>
    
    <p>learn about <a href="https://docs.microsoft.com/aspnet/core">building web apps with asp.net core</a>.</p>
</div>

这时,当我们运行起程序后就会看到如下图所示的效果:

自定义功能开关

最后要介绍的是如果创建和使用自定义的功能开关,笔者这里做一个这样的示例,当网站被 microsoft edge 浏览器访问时,显示功能,其余浏览器则隐藏功能。

这里,笔者创建一个配置的映射类 browserfiltersettings 和执行过滤的操作类 browserfeaturefilter,示例代码如下所示:

public class browserfiltersettings
{
    public string[] allowedbrowsers { get; set; }
}

[filteralias("browserfilter")]
public class browserfeaturefilter : ifeaturefilter
{
    private readonly ihttpcontextaccessor _httpcontextaccessor;
    public browserfeaturefilter(ihttpcontextaccessor httpcontextaccessor)
    {
        _httpcontextaccessor = httpcontextaccessor;
    }
    public task<bool> evaluateasync(featurefilterevaluationcontext context)
    {
        var useragent = _httpcontextaccessor.httpcontext.request.headers["user-agent"].tostring();
        var settings = context.parameters.get<browserfiltersettings>();
        return task.fromresult(settings.allowedbrowsers.any(useragent.contains));
    }
}

接着,进行功能开关的注入,示例代码如下所示:

public void configureservices(iservicecollection services)
{
    services.addhttpcontextaccessor();
    services.addfeaturemanagement()
        .addfeaturefilter<browserfeaturefilter>();
    services.addcontrollerswithviews();
}

然后,进行功能开关的配置,示例配置如下所示:

  "featuremanagement": {
    "browserflag": {
      "enabledfor": [
        {
          "name": "browserfilter",
          "parameters": {
            "allowedbrowsers": [
              "edge"
            ]
          }
        }
      ]
    }
  }

接着,使用方式如下所示:

@using microsoft.featuremanagement
@inject ifeaturemanager featuremanager
@addtaghelper *,microsoft.featuremanagement.aspnetcore
@{
    viewdata["title"] = "home page";
}

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

    <feature name="browserflag">
        <h2>i am a browser flag only on edge!</h2>
    </feature>
    
    <p>learn about <a href="https://docs.microsoft.com/aspnet/core">building web apps with asp.net core</a>.</p>
</div>

这时,当我们分别用 microsoft edge 和 google chrome 访问站点时就会看到如下图所示的效果:

总结

借助于 microsoft.featuremanagement.aspnetcore 扩展包,我们可以很容易实现 feature flag 效果。又由于这种实现是基于 iconfiguration 的,所以很具有通用性。这里列出官方给出的优点:

  • a common convention for feature management
  • low barrier-to-entry
    • built on iconfiguration
    • supports json file feature flag setup
  • feature flag lifetime management
    • configuration values can change in real-time, feature flags can be consistent across the entire request
  • simple to complex scenarios covered
    • toggle on/off features through declarative configuration file
    • dynamically evaluate state of feature based on call to server
      api extensions for asp.net core and mvc framework
    • routing
    • filters
    • action attributes

非常感谢你能阅读这篇文章,希望它能对你有所帮助。

相关参考

  • featuremanagement-dotnet
  • microsoft.featuremanagement
  • tutorial: use feature flags in an asp.net core app
  • using custom feature flag filters in .net core