.Net Core功能开关实战

实战.Net Core如何通过FeatureToggle实现发布功能开关、以及业务功能开关

0
3223

背景:

为了快速发布开发完成的功能,企业通常会以比较快的迭代周期持续发布。但是由于某些 原因或场景,需要在发布的时候将某些功能隐藏起来或者小流量的开放(例如只有某些特定用户可以使用、或者特定日期开放),通过使用功能开关(feature toggle\feature flag)可以很好的解决这个问题,接下来小编将以.net core为例给大家介绍此功能的具体实现。

Feature Toggle介绍

Feature Flag (又名 Feature Toggle、Feature switch、Flip等) 是一种可以通过配置(配置文件、数据库等)或自动化(特定用户、特定时间等)控制线上功能开启或者关闭的方式。核心思想就是将功能的开发和代码的发布解耦。

发布功能开关:

通过发布功能开关,开发团队可以将未完成的功能在发布时设置为隐藏,这样就可以持续的发布新功能到生产也不会影响到用户的使用,同时避免了一个复杂功能需要较长开发周期导致的在合并代码的时候出现各种冲突难以解决的问题。直到新功能稳定,开启功能开关或者删除对应功能开关来完成功能上线。

业务功能开关:
  • 实现A/B测试。
  • 针对特定人群发布功能尽早获得反馈。
  • 针对特定条件开启或者关闭功能。例如可以在特定时间、特定地域、特定人员开启,能线上开启或者关闭,实现快速回滚。

.Net Core实战:

开源框架:

网上搜索有很多相关的开源框架,例如(NFeature、FeatureSwitcher、nToggle、Feature Toggle等),这里小编使用了Jason Roberts提供的开源Feature Toggle框架 – https://github.com/jason-roberts/FeatureToggle,原因是,安装简单、支持不同的.net 平台,默认提供了很多Toggle Provider可以直接使用,而且拓展方便。

支持的.NET平台:

– Net Desktop/Server Applications
– Windows Store Apps && Windows Phone Silverlight Apps

已提供的Toggle:

– AlwaysOffFeatureToggle(Hardcode功能默认为关闭,如果需要更改必须重新编译程序)
– AlwaysOnFeatureToggle(Hardcode功能默认为开启,如果需要更改必须重新编译程序)
– SimpleFeatureToggle(通过配置文件开启或关闭相应功能web.confg\app.xaml\appsetting.json)
– EnabledOnOrAfterDateFeatureToggle(在特定日期开启功能)
– EnabledOnOrBeforeDateFeatureToggle(在特定日期开启功能)
– EnabledBetweenDatesFeatureToggle(在特定日期内功能开启)
– EnabledOnDaysOfWeekFeatureToggle (在一周的特定日期开启功能,比如周末 )
– RandomFeatureToggle (自动随机的开机或关闭某个功能)
– SqlFeatureToggle (通过读取数据库完成开关配置)

Feature Toggle配置

这里小编将选取几个比较有代表性的Toggle来演示具体实现,比如RandomFeatureToggleSimpleFeatureToggle,其他的大家可以自己研究下。
以及演示如何创建自定义”功能SpecificUsersFeatureToggle”并应用。
DEMO源码地址:https://github.com/leansoftX/FeatureToggleDemo

安装Feature Toggle包:

点击 管理Nuget程序包 | 搜索Feature Toggle | 选择包 | 点击安装

Demo1 – 随机开启或关闭功能:

本实例将演示如何通过继承RandomFeatureToggle类,实现功能的随机开机或关闭。
例如这里小编实现了随机显示或隐藏当前程序中“微信通知”功能。

  1. 添加功能类 – WechatNotifyFeature.cs, 如下图所示

  2. 选择需要使用的Toggle Provider,在WechatNotifyFeature类中,添加引用:using FeatureToggle; 并继承类: RandomFeatureToggle,代码如下:
using FeatureToggle;  
namespace LeansoftX_FeatureToggle.Models.FeatureToggles 
{ 
     public class WechatNotifyFeature:RandomFeatureToggle { } 
}

3. 添加HomeViewModel,并添加功能开关属性,获取功能开关值(返回 True or False)

using FeatureToggle; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading.Tasks; 
using LeansoftX_FeatureToggle.Models.FeatureToggles; 
namespace LeansoftX_FeatureToggle.Models 
{ 
    public class HomeViewModel 
    { 
        public WechatNotifyFeature WechatNotifyFeature 
        { 
            get { return new WechatNotifyFeature(); } 
        } 
    }
}

4. 修改Home控制器,代码如下

public IActionResult Index() 
{ 
    return View (new Models.HomeIndexViewModel()); 
}

5. 修改视图,代码如下:

@model LeansoftX_FeatureToggle.Models.HomeIndexViewModel 
@if (Model.WechatNotifyFeature.FeatureEnabled) 
{ 
    <li>
        <a asp-area="" asp-controller="Home" asp-action="About">微信通知</a>
    </li> 
}

6. 启动应用效果如下(微信通知菜单随机显示或隐藏):

Demo2 – 通过配置文件控制功能开启或关闭

通常我们更希望的是通过配置文件来决定某个功能是否需要开启,这里可以通过继承SimpleFeatureToggle类加配置文件的方式实现功能的开启或关闭。
这里小编实现了配置“邮件通知”功能的显示或隐藏。

  1. 添加功能类: EmailNotifyFeature.cs , 如下图所示:
  2. 在EmailNotifyFeature类中,添加引用:using FeatureToggle;,并继承类: SimpleFeatureToggle

    using FeatureToggle;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    namespace LeansoftX_FeatureToggle.Models.FeatureToggles
    {
        public class EmailNotifyFeature: SimpleFeatureToggle
        {
    
        }
    }
  3. 修改Startup.cs,在ConfigureServices方法中添加如下代码,设置功能开关使用Appsetting.json配置文件作为Provider:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using FeatureToggle.Internal;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using LeansoftX_FeatureToggle.Models.FeatureToggles;
    namespace LeansoftX_FeatureToggle
    {
        public class Startup
        {
            public IConfigurationRoot Configuration { get; }
            public Startup(IHostingEnvironment env)
            {
                var builder = new ConfigurationBuilder()
                              .SetBasePath(env.ContentRootPath)
                              .AddJsonFile("appsettings.json", optional: 
                               false, reloadOnChange: true)
                              .AddJsonFile($"appsettings.{env.EnvironmentName}
                              .json", optional: true)
                              .AddEnvironmentVariables();
                Configuration = builder.Build();
            }
            // This method gets called by the runtime. Use this method to 
               add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                var provider = new AppSettingsProvider 
                             { Configuration = Configuration };
                services.AddSingleton(new EmailNotifyFeature 
                                     { 
                                         ToggleValueProvider = provider 
                                     });
                services.AddMvc();
            }
    
            // This method gets called by the runtime. Use this method 
               to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironmen
            t env)
            {
                if (env.IsDevelopment())
                {
                    app.UseBrowserLink();
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Home/Error");
                }
                app.UseStaticFiles();
                app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Home}/{action=Index}/{id?}");
                });
            }
        }
    }

4. 修改配置文件Appsetting.json,添加FeatureToggle节点,并配置EmailNotifyFeature功能开启或者关闭。

{
  "FeatureToggle": {
    "EmailNotifyFeature": "true"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

5. 修改HomeContoller,代码如下

private readonly EmailNotifyFeature _emailNotifyFeature;      
public HomeController(EmailNotifyFeature emailNotifyFeature)
{
    _emailNotifyFeature = emailNotifyFeature;
}
public IActionResult Index()
{
    ViewBag.EmailNotifyFeature = _emailNotifyFeature;
    return View(new Models.HomeIndexViewModel());
}

6. 修改视图代码如下:

@model LeansoftX_FeatureToggle.Models.HomeIndexViewModel 
@{ 
     var emailNotifyFeature = (LeansoftX_FeatureToggle.Models
                             .FeatureToggles.EmailNotifyFeature)
                             ViewBag.EmailNotifyFeature; ViewData["Title"] 
                             = "LeansoftX"; 
     Layout = null; 
} 
<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li>
            <a asp-area="" asp-controller="Home" asp-action="Index">主页</a>
        </li> 
        @if (Model.WechatNotifyFeature.FeatureEnabled) 
         { 
        <li>
            <a asp-area="" asp-controller="Home" asp-action="About">
               微信通知
            </a>
        </li> 
         } 
        @if (emailNotifyFeature.FeatureEnabled) 
         { 
            <li>
            <a asp-area="" asp-controller="Home" asp-action="Email">
                邮件通知
            </a>
            </li> 
          } 
    </ul> 
</div>

7. 开启功能,效果如下:

8. 关闭功能,效果如下:

Demo3 – 自定义功能开关

现有的 ”开关” 往往不能满足我们的实际需求,我们可以继承IFeatureToggle接口实现自定义的 ”开关”, 这里小编实现了“短信通知”功能只对特定用户开放。

  1. 添加自定义开关 – SpecificUsersFeatureToggle,添加引用using FeatureToggle; ,继承并实现接口IFeatureToggle,代码如下: 继承自此类的“功能”只针对用户”jackyzhou”或者”leixu”开放。

注意:这里代码逻辑是随机返回一个用户,具体实现请下载Github实例代码查看。 大家可以根据实际需求改为读取“数据库特定权限用户”或者“付费用户”等其他方式。

public class SpecificUsersFeatureToggle : IFeatureToggle
{
    public bool FeatureEnabled 
    {
        get {
                var user = new Models.Users().GetUser();
                if (user.Name == "jackyzhou" || user.Name == "leixu")
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
    }

2. 添加功能类 – SMSNotifyFeature

3. 在SMSNotifyFeature类中,添加引用:using FeatureToggle;,并继承类: SpecificUsersFeatureToggle, 代码如下:

public class SMSNotifyFeature:CustomToggles.SpecificUsersFeatureToggle    
{    
}

4. 在View Model中添加属性,如下代码:

public class HomeIndexViewModel 
{ 
    public WechatNotifyFeature WechatNotifyFeature 
    { 
        get { return new WechatNotifyFeature(); 
    } 
    public SMSNotifyFeature SMSNotifyFeature 
    { 
        get { return new SMSNotifyFeature(); } 
    }  
}

5. 修改视图,代码如下:

@if (Model.WechatNotifyFeature.FeatureEnabled) 
{ 
    <li>
        <a asp-area="" asp-controller="Home" asp-action="About">微信通知</a>
    </li> 
} 
@if (emailNotifyFeature.FeatureEnabled) 
{ 
    <li>
        <a asp-area="" asp-controller="Home" asp-action="Email">邮件通知</a>
    </li> 
} 
@if (Model.SMSNotifyFeature.FeatureEnabled) 
{ 
    <li>
        <a asp-area="" asp-controller="Home" asp-action="SMS">短信通知</a>
    </li> 
}

6. 设置断点,并启动应用,效果如下(当前用户为user02, 所以短信功能不可见):


7. 设置断点,并启动应用,效果如下(当前用户为leixu, 所以短信功能为可见):

总结:

通过FeatureToggle我们可以持续的将新功能发布到生产环境,并通过开关灵活控制每个功能显示与隐藏,可以是特定环境开关、特定时间开关、特定地域开关、或者特定人员开关。可以帮助我们实现A/B测试,线上小流量测试。

通过FeautreToggle我们可以持续的将未完成的功能合并到主干分支,并对用户隐藏,避免了一个复杂功能需要较长开发周期导致的在合并代码的时候出现各种冲突难以解决的问题。

另外小编在本章中只是demo了如何对前端逻辑实现功能隐藏,实际应用过程中也需要对业务逻辑进行控制。