引言
特性切换是现代软件开发中的常见模式,通过灵活的功能管理,使得团队能够更高效地进行开发、测试和发布。特性切换 Feature Toggles/ Feature Flag 作为一种高效的功能管理技术,能够在应用运行时动态启用或禁用功能,而无需更改代码。简单来说,特性切换就像是一个“开关”,开发者可以根据需要灵活控制某些功能的开启与关闭,极大地提升了开发和部署的效率。
特性切换的基本概念可以被理解为一个简单的 if/else 语句,可以在运行时根据条件判断来控制程序行为。通过配置文件或代码中的开关,可以在运行时启用或禁用某个功能。这种做法使得开发者能够在不需要重新发布应用的情况下,实时调整软件的功能。除了简单的开/关控制外,特性切换还可以根据不同的用户、环境或时间动态变化,满足多种需求,如渐进式发布、A/B 测试等,还能让团队在功能开发过程中灵活控制新特性的发布,从而避免频繁的发布和回滚。
1. 特点:
- 在运行时动态启用或禁用功能,而无需更改代码。
- 可以基于条件(如用户角色、地理位置等)控制某些功能的开启。
- 特性切换通过灵活的配置管理,使得开发团队能够轻松控制功能的发布与回滚,提升迭代速度。
2. 特性切换的优势
- 灵活性:特性切换使得开发团队能够在应用运行时启用或禁用某些功能,而无需发布新版本。这大大提高了灵活性,可以即时响应用户反馈和市场需求。
- 降低风险:在发布新功能时,特性切换允许开发者逐步启用新功能或进行渐进式发布,降低了大规模发布时出现故障的风险。
- 加速开发周期:特性切换帮助开发团队避免长时间的功能分支和合并过程,可以快速迭代并进行小范围的功能测试和验证。
- 提高用户体验:根据不同用户群体或特定条件,特性切换能够精准控制某些功能的发布,提供个性化体验。
3. 常见的特性切换类型
特性切换有多种类型,根据不同的应用场景可以选择不同的方式进行实现:
- 开/关开关(On/Off Toggles):用于简单的功能启用/禁用。在团队协作时,可以在同一代码库中开发不同的功能,而不必担心某些功能的未完成部分影响整体发布。
- 权限开关(Permission Toggles):基于用户角色、权限等特定条件启用或禁用功能。例如,根据用户的订阅级别或账户状态来决定是否开启某个功能。
- 渐进发布(Canary Toggles):在多阶段发布中使用,通过逐步将功能推出给用户,先在小范围内进行验证,逐渐扩大到所有用户。
- 实验性切换(Experiment Toggles):用于 A/B 测试等实验功能,通过特性切换实时调整功能,评估不同版本的表现。A/B 测试常常基于用户群进行统计学分组,其实现使用到hash算法进行分桶,针对不同桶设置不同的参数。
- 环境切换(Environment Toggles):根据不同的环境(如开发、测试、生产环境)启用不同的功能,确保各环境之间的功能差异。
Martin Fowler 根据有效时长和动态性对于特性切换做了分类,如下图:
4. 举例
Togglz 是一个流行的特性切换类库,提供了一些高级功能,例如:
- Username:根据用户名启用特性,支持特定用户的功能切换。
- Gradual Rollout:通过逐步启用功能,进行 Canary 发布,评估新功能的表现。
- Release Date:可以在指定日期启用特性,适用于定时发布、促销活动等场景。
- Client IP:基于客户端 IP 启用功能,可以根据地理位置或其他因素控制功能发布。
Togglz 提供了与 Spring Boot 的集成,可以快速在项目中启用特性切换,支持多种激活策略和灵活的配置管理。
// 特性为枚举类型
public enum MyFeatures implements Feature {
@Label("First Feature")
FEATURE_ONE,
@Label("Second Feature")
FEATURE_TWO;
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
}
public void someBusinessMethod() {
// 通过第三方(依赖)实现特性管理,避免耦合,方便测试
// 使用时同使用 boolean 一样
if( MyFeatures.FEATURE_ONE.isActive() ) {
// do new exciting stuff here
}
[...]
}
Togglez 使用策略模式表示求取开关的过程,包括动态求值(动态表达式求值)和静态求值,不妨看下其基于用户角色的策略实现:
/**
* ActivationStrategy implementation based on roles of the current user. As far as user has at least one of configured roles
* then feature will be active.
* <p/>
* Please note this activation strategy is not coupled to any particular security framework and will work with any framework as
* far as current user has "roles" attribute populated with a set of granted authorities. This is usually a responsibility of an
* UserProvider.
*
* @author Vasily Ivanov
*/
public class UserRoleActivationStrategy implements ActivationStrategy {
// 略去其他
@Override
public boolean isActive(FeatureState state, FeatureUser user) {
if (user != null) {
Collection<String> userRoles =
(Collection<String>) user.getAttribute(USER_ATTRIBUTE_ROLES);
if (userRoles != null) {
String rolesAsString = state.getParameter(PARAM_ROLES_NAME);
if (Strings.isNotBlank(rolesAsString)) {
List<String> roles = Strings.splitAndTrim(rolesAsString, ",");
for (String authority : roles) {
if (userRoles.contains(authority)) {
return true;
}
}
}
}
}
return false;
}
@Override
public Parameter[] getParameters() {
return new Parameter[] {
ParameterBuilder.create(PARAM_ROLES_NAME)
.label(PARAM_ROLES_LABEL)
.description(PARAM_ROLES_DESC)
.largeText()
};
}
}
实现和一些权限管理框架类似,开发者需要自行决定这种实现的单一职责归谁所有。
OpenFeature 是一个开源的特性开关规范,其做了很多有益的抽象,提出了服务提供方、求值API、钩子、事件、追踪等定义:
提供的使用例子如下:
package com.demo;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.OpenFeatureAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RestHello {
private final OpenFeatureAPI openFeatureAPI;
@Autowired
public RestHello(OpenFeatureAPI OFApi) {
this.openFeatureAPI = OFApi;
}
@GetMapping("/hello")
public String getHello() {
final Client client = openFeatureAPI.getClient();
// Evaluate welcome-message feature flag
if (client.getBooleanValue("welcome-message", false)) {
return "Hello, welcome to this OpenFeature-enabled website!";
}
return "Hello!";
}
}
实现基本思想和 Togglez 一致,即引入中间层实现解耦,最终使用布尔值表示特性是否打开。
5. 最佳实践
为了有效使用特性切换,开发团队应遵循一些最佳实践:
- 统一命名与管理:统一特性切换的命名规则和管理策略,避免混乱。不同项目应遵循相同的特性切换实现范式。
- 清理过期的切换:定期清理不再需要的特性切换,减少技术债务。
- 确保充分测试:在所有可能的特性切换状态下都进行充分的测试,避免因状态未覆盖的部分导致的问题。
- 监控与分析:运行时监控特性切换的效果,确保不会引入不必要的负担。逐步启用新功能并监控其影响,避免一次性大规模推出。
- 注意复杂性和技术债:开发者需要小心管理特性切换的数量和复杂度,确保其不会带来过多的技术债务。