功能切换是一种工具,可让您从旧功能切换到新功能,而无需重建应用程序并再次释放它。 它是通过在代码中添加条件运算符(
if
)来实现的,从而可以通过简单地更改配置文件或数据库中的所需值来控制程序的行为。 如果您曾经在ini文件中编辑过设置,那么您就熟悉此技术。
深入研究,您可以找到有关开关及其提供的新功能的大量不同选项。 这就提出了许多问题。 在哪里放置配置? 但是,如果无法使用该怎么办? 也许您可以自己编写一个简单的框架来使用交换机? 也许最好采用现成的解决方案? 这是否适合整体服务和微服务?
该材料包含有关在.NET平台上进行开发的上下文中的功能开关的基本信息。 第一部分包含有关交换机的一般信息; 它们完全独立于特定的实现,对于使用各种平台的专业人士可能有用。 第二部分讨论了特定的现代工具,这些工具有助于在开发.NET时使用开关。
我试图写一篇文章,以帮助您决定是否在您的特定情况下实现功能开关,以及在必要时决定如何实现。 到目前为止,在我们部门中,开关仅用于解决特定问题。 例如,我们的应用程序经常需要从目录中请求法律和会计信息,可以以两种形式来表示:远程完整目录及其本地部分副本。 在应用程序设置中,用户可以选择应用程序当前应使用的目录类型。 该计划包括将交换机作为整个项目的通用基础结构引入。 基于此,将建立与上述类似的过程。
功能开关概述
这是什么
简而言之,开关的含义如下。 承包商通过我们代表主题领域逻辑的代码前进,并偶然发现了条件声明。 在此声明中,承包商询问是否需要从某个注册表中执行一些条件代码,该注册表知道何时以及在什么情况下应该起作用。
结果,执行器要么沿着一个分支走,要么沿着另一个分支走,而在退化的情况下,另一个分支只是空着。 在最简单的情况下,该开关在维护注册表的负责人的控制下采用两个值(“ on”和“ off”)之一。 而且您可以提出更复杂的内容,例如,仅包含特定用户或特定国家/地区或特定时间的功能。
功能注册表可以采用多种形式。 关键是您可以在不停止应用程序或重新部署它的情况下管理此注册表。 例如,交换机可以存储在文件中,数据库中,或者具有带有交换机的单独的网络服务。 下面将更详细地考虑开关的操作。
这甚至相关吗?
featureflag.tech 的创建者在其
文章中声称,开关已成为软件开发中的标准做法。 关于转换的材料越来越多:理论文章,有关实施转换的实践的故事,会议报告。 我在本文中包含了最有趣的资料的链接; 所有链接都在
“结论”部分中收集。
我想说,在信息技术的某些领域中,稳定和标准化促进了交换机的普及。 在“血腥企业”中出现了越来越多的稳定孤岛。 开发,组装的自动化,应用程序的检查和交付中度量标准数量的增加导致开发过程变得更加透明和易于管理。 结果,对于软件创建的新要求开始出现。 这些要求之一是希望及时发布应用程序的发布时间并包含新功能,这些功能由交换机来实现。 工业能力已经成熟以满足这种工业需求。
至于有助于与交换机配合使用的工具,其中有很多,并且适用于各种平台。 可能通过实现开关的基本功能的简单性可以解释大量工具的外观。 但是,添加更复杂和“美味”的面包可能需要大量的人工成本,因此许多实现功能转换的开源项目停止了开发并得到支持。 但是,仍然有大量的项目(收费和免费项目)仍在进行中。
本文的
第二部分描述了其中最有趣的(从.NET的角度来看)。
多一点
本文有一个很好的图表说明了开关的操作。 我建议您熟悉一下。
我们从代码开始考虑电路。 在我们的示例中,提供一个新功能-检查填写文档的正确性。 我们确保仅在启用此功能后才检查文档。 让我们做一个切换点:
var document = GetDocument(); if (feature.IsEnabled("Feature #123. Document validation")) { Validate(document); }
在这种情况下,
feature
是对基础结构的引用,可帮助我们的应用程序与路由器和功能注册表进行通信。 使用此基础结构,我们可以确定是否有必要检查文档,如果需要,则进行检查。
路由器使用可用的信息:作为参数传递的功能的名称,以及可以从其知道的静态类中提取的内容,以确定在这种情况下请求的功能是否应该起作用。 路由器找出注册表中的交换机是如何配置的。 如果注册表不可用,则必须使用以前缓存的数据,或者针对这种情况采取其他策略。 您还可以想象一个路由器通过该方法的参数显式接收其他信息(上下文),以帮助其决定是否当前启用功能。
负责人员以选定的方式配置交换机。 在最令人愉快的选择中,它们会转到代表交换机注册表的站点,然后用鼠标单击所选的交换机。
因此,就工件而言,交换系统由两部分组成。 一方面,这是程序员的基础架构,可用来确定功能是否在这种情况下应该起作用,并沿着相应的代码分支直接执行。 另一方面,它是一种机制,允许负责任的员工选择要启用和禁用的功能。 在退化的情况下(将注册表缝入代码中时,请参见下文),两个工件可能会重合,并且程序员可以控制打开和关闭功能。
可能的开关系统要求
如您所见,交换系统可能是一件相当复杂的事情。 我们列出了可能对这样的系统施加的基本要求。
- 不同参数的打开和关闭功能的依赖性。 参数示例:用户(角色,标识符,地理位置),时间(白天或黑夜,工作或工作日,特定时间段),环境(用于开发,测试,工业操作)。
- 分析域流程时使用情况统计信息的收集:哪个用户在什么条件下看到或看不到功能,多少次使用了新功能。
- 审核开关自身发生的情况:谁按下开关,何时,发生什么变化。
- 在远程注册表不可用的情况下,可以在本地和远程注册表功能或某些策略之间灵活选择。
- 为客户和承包商的不同类别的员工配置对交换机系统的访问权限:技术专家,主题领域的专家以及应用程序用户本身。
当然,不必满足所有这些要求。 相反,在许多情况下,某些要求可能是不希望的。 您需要遵守的要求越多,开发自己的交换系统显然就越麻烦。 这意味着在引入交换机之后的某个时候,您自己的系统的支持将变得无济于事,现在是时候采用现成的解决方案了。
科技树
我们总结了有关功能开关的已考虑信息,并借助视频游戏中
技术树的隐喻。 让我们将交换机想象成一项技术,该技术使该公司可以推出新功能,而不必依赖于组装和部署应用程序的时刻。 该技术的成本(维护交换机和注册表的生命周期的成本)是已知的,并且具有实施的先决条件:灵活的开发方法和持续集成。 当然,连续集成不是前提条件,但是它使交换效率更高,并允许连续交付应用程序。 此外,对交换机的“研究”“开放”了其他技术-A / B测试和Canary发布。 它们将在下面讨论。

功能注册表可能位于的位置
考虑几种用于放置功能注册表的选项,这些选项使实现的复杂性增加。
- 直接将配置缝入代码中,例如,注释掉不必要的代码,取消注释必要的代码,或使用条件编译指令。 显然,此选项扼杀了引入开关的本质,因为使用该选项,您将需要重新编译并部署应用程序以查看新功能。 还应注意两个困难。 首先,采用这种方法,具有功能的员工将需要其他技术技能:编辑源代码以及使用版本控制系统(SLE)的能力。 上帝当然知道什么技能,但是程序员很可能必须包括功能。 其次,该功能在发布此版本应用程序的所有节点上都相同-要在旧功能和新功能之间进行选择,您将需要一个平衡器和该应用程序的多个节点。
- 将配置放置在应用程序环境中,例如,环境变量中。 这个选项似乎有点奢侈,因为它充满了对运行时或操作系统的过度依赖。
- 使用配置文件,这是存储应用程序设置的相当标准的位置。 此选项的缺点是需要在应用程序的每个实例旁边分别维护配置文件。 此缺点是先前版本中固有的。
- 在数据库中维护一个表,该表描述功能开关。 应用程序实例将打开此数据库,以查看它们感兴趣的功能是否起作用。 在此选项中,注册表是集中式的(与以前的版本不同),但是如果所选的交换器基础结构支持,则可以为每个节点分别包括功能。
- 提高应用程序实例将通过选定的网络协议访问的网络服务。 如果在以前的版本中,这意味着交换机将最有可能与主题区域的实体一起存储,因此轮询交换机的成本是可以预测的,那么这里我们将通过网络进行其他访问。 额外处理的成本和拒绝服务的可能性是此选项的严重缺陷,但是,当然不禁止缓存。 对于失败,您必须提供默认行为。
推荐建议
关于开关的使用,可以提出以下一般建议。
开关点和逻辑不必在一起 。 在最简单的示例中,在条件语句(我们查询特定开关的状态)之后,代码立即实现了所包含的功能。 这不是很方便,因为它为逻辑添加了冗余的依赖关系:有关交换机基础结构的知识以及来自注册表的特定功能的名称。 实际上,当不需要开关时,这会使开关的测试和拆卸过程复杂化。
设置更高的开关点 。 上一段的发展。 交换基础结构访问的点应从主题区域的逻辑中排除到最后可能的机会,也就是说,应将它们“推”到更靠近接收请求的位置。 因此,我们减少了各个模块对交换机的依赖性,简化了其支持和测试过程。
使用策略代替条件语句 。 如果开关要长期使用,则可以将条件运算符改进为一种策略,并将可切换逻辑明确隔离为单独遵循和测试的内容。
不要对开关进行分组 。 您不应该在交换机之间形成依赖关系,对其进行分组并在层次结构中进行组装。 这将使维护具有新功能的代码以及交换机本身的生命周期变得更加容易。
合并一个功能的开关点 。 事实证明,多个程序块的行为一次取决于同一开关。 如果开发的协调性不佳,则可以在不同的地方复制切换点,这将导致更昂贵的测试和支持。 如果您受过训练,尝试将切换点“推”到应用程序的入口,那么通常不会自动执行此建议(不要在系统中分散点)。
开关的主要类别
考虑
本文给出的开关分类。 它基于开关的“存活”时间以及其状态改变的频率。 将开关分配给特定类有助于确定如何在代码中使用该开关以及如何存储该开关的状态。
释放开关这是交换机的主要视图。 它们使您可以将开发集中在一个主要分支上,此外,该分支还可以定期推广到商业运营中。 我们没有在单独的分支中进行开发并将其注入主分支以发布所需的版本,而是在代码中引入了一个切换点,该切换点隐藏了用户尚未使用的功能。 准备就绪后,交换机将启动新功能。
这些开关的使用寿命为数天或数周,而新功能的开发和实施仍在进行中。 在对功能进行了测试并认为合适时,可以删除代码中的开关和开关点。 当发布新版本或更改应用程序配置时,开关的状态通常会更改。 允许将这样的开关存储在配置文件中。 新功能的切换点很可能是唯一的切换点。 最好不要以常规条件语句的形式进行挖掘和放置。
实验开关为了以新功能运行,使用的开关的使用寿命为几天到几个月。 在实验过程中,您可以监视用户如何感知更改以及他们的行为如何更改。 期望开关的状态可以随着每个请求而非常动态地改变。 一些集中式存储(例如数据库或网络服务)在这里更适合。 , .
, . , , . «», , , .
, , . , , , . , .
— . ( ), , .
, (, , ) . . - .
,
, ( , ), . .
A/B-. , .
. , .
- . , .
. , .
. , . .
. , .
. , .
A/B-, A/B-. A/B- - . : 1) , 2) , , 3) , . , , . ? ? «»?
, A/B-, , — . , , - - . , . , , - , .
. , . , A/B-. .
,
«»,
«Feature Toggles, » , A/B- .
-? : , , . . , , , .
( ).
, , « ». , (. . ), , , , , , . , , , : « ». -, , ,
. , .
. : - , «» — , - , ( «»). . . , , . , — , . , .
, . . : , 1 %. , , , , , , . , . , .
- , . , . , . , , .
- , , , . , . .
, - . - , , . , , , .
- —
«»,
. - —
. -
.
, , , : , , , , . . , , , , , , , .
,
, . , , . , ( ), .
. , () . , , — . : , !
- , . , . , . ( ) , .
MongoDB DynamoDB.MongoDB DynamoDB. DynamoDB: ( ) MongoDB. — , , , — . (DynamoDB).
, MongoDB, — DynamoDB. , DynamoDB ( MongoDB). , DynamoDB .

DynamoDB , MongoDB. - MongoDB. , . , («» «»), . - DynamoDB . , , . — .
MongoDB, DynamoDB. , , , , , , . - , , MongoDB, , . , , , . , .
, . , , DynamoDB. MongoDB, .
仅此而已。 , , , DynamoDB , MongoDB , DynamoDB 100 %. , .
«Feature Toggles, » ( !) , . .
, . , - ( , ), , . — , . , , API «» . , , — .
, . , . , « ».
. , .
.NET
. -, (), , , , , . ( ). -, , , REST API. ( ) , API .NET. -, ( ) .NET. , .
, , . , .
Apache ZooKeeper ,
Consul etcd ,
Spring Cloud Config Server, .NET . , , . - , , . .
LaunchDarkly, «» . , , , . , , Jira Visual Studio Code. . , , , 30- .
Catamorphic Co., ,
- .
Microsoft.FeatureManagementMicrosoft .NET Core, : -, , , -, , Azure. , , Azure App Configuration,
API .
ASP.NET Core, , .
, Microsoft.FeatureManagement.
Rollout, . , , , . ( , 14- ) REST API. , , . .
OptimizelyOptimizely — - .
Rollouts , . , , («» ), . , . , . — .
Bullet train. - — . .
Split, . «» (A/B-).
ConfigCat, . . , REST API. .NET . , .
Moggles, . SQL Server. .
伺服器
Unleash, . . . ; PostgreSQL.
.NET , . , API, , .
GitLab feature flagsGitLab Unleash, GitLab , Unleash. Unleash , ( GitLab), : GitLab Premium ( GitLab ) GitLab Silver ( GitLab ).
Feature flags API in GoAPI ( ), . , . Go; , , , . . : 20 2019.
Bandiera. API . Ruby; MySQL PostgreSQL, Docker. Ruby, Node, Scala, PHP, .NET. .
FlagrGo. Docker. , Ruby, Go, JavaScript, Python. . .
.NET , . . , 2019 . . .
Esquio, . — , EF Core. ASP.NET Core ( Microsoft.FeatureManagement); , Azure .
FeatureSwitch, (
FeatureSwitch.Strategies
). , . , , «/».
Toggle.Net, . , «/».
RimDev.FeatureFlagsASP.NET Core. , , , . — SQL Server.
( ) .
«» .
. , , .
. , , , .
. , A/B-.
. .
. .
结论
, . . , , A/B-.
, , , .
( ) .
- , — , , .
- , . , .
- , . , , , , .
- , . . , «» .
- , . , . , .
- . , . : , . , , .
- . , , . : , , , , . : ( ), .
- . ? , ? . , , . ? ? : , .
- — , . , . : (); ( ); .
- — , . : (); «/».
- , . : ; (, ).
- . , ? : , , , ( ).
( )
( Medium)
( HighLoad++)
- ( )
- («»)
(«»)
(«»)
- ( Featureflags.io)
( Featureflags.io)
Microsoft ( )
( )
( Smithsonian)
(«»)