无论您是否喜欢,软件开发都是一项协作活动。 整合工作一直被妖魔化,并被视为必不可少的邪恶。 有几种方法可以解决有效集成的挑战。 功能切换开关属于该组。 在本文中,您将在实践中看到如何在Spring Boot应用程序中使用功能切换(也称为功能标志)。
1.什么是功能切换?
简而言之, 功能切换是允许根据其当前值在应用程序中执行替代路径的变量。 通过保持不同的执行方案,您可以在不更改代码的情况下修改应用程序的行为。
根据您的需求,可以在启动应用程序之前设置切换开关的值,也可以在运行时对其进行调整。 在后一种情况下,值的更改可以保留或仅影响应用程序的当前执行。
通常,您会读到有关功能标志的信息, 以作为功能源代码分支的替代方法 ,但是,实际上,两种技术可以一起使用。 例如,您可以使用功能分支在应用程序中开发新的用户故事,同时可以使用功能切换来控制对不同环境(例如,具有不同要求的客户端)上功能的访问。
尽管有许多用途,功能切换也有其缺点。 最大的问题是复杂性 。 如果没有适当的策略,他们可能会Swift失控,成为维护的噩梦。 幸运的是,如果您遵循几种良好的做法并围绕features来组织应用程序 ,则使用Feature标志应该更加简单。
2.使用功能切换选择豆
在Spring Boot应用程序中使用功能切换的最常见情况是基于功能切换的当前值激活某些接口的不同实现。 我们来看一个示例来说明所描述的情况。
2.1依赖抽象
假设您有一个Web端点,该端点返回从数据库存储库中获取的产品列表。 您的目标是创建一个功能切换,该功能切换允许将存储库实现切换为使用Web服务作为数据源的实现。
如果要允许要素切换的类直接在其他类中使用,则您要做的第一件事是使用接口抽象依赖关系。
下面的代码片段提供了一个示例Product REST端点,该端点依赖于ProductRepository接口。
@RestController
@RequestMapping("/products")
class ProductController {private final ProductRepository productRepository;ProductController(ProductRepository productRepository) {this.productRepository = productRepository;}@GetMappingCollection<Product> getAll() {return productRepository.findAll();}}
目前,我们只有一个接口实现。 不久,我们将添加另一个,您将通过功能切换激活它。
@Repository
class DbProductRepository implements ProductRepository {//...
}
2.2 application.properties中的功能切换
由于application.properties文件用于配置Spring Boot应用程序,因此是放置功能切换标志的好地方。
feature.toggles.productsFromWebService=true
在提交代码之前,将标志设置为false。 这样,默认情况下,您的队友将禁用新功能。 如果有人要激活该功能,则他们可以在本地开发环境中将标志值更改为true。
2.3有条件的Bean创建
下一步是创建要通过功能切换激活的接口的替代实现。 为了根据创建的属性的值实例化bean,可以使用Spring Boot注释@ConditionalOnProperty 。 设置切换属性的名称和应激活它的值。 该值应与放在application.properties文件中的值相同。
@Repository
@ConditionalOnProperty(name = "feature.toggles.productsFromWebService",havingValue = "true"
)
class WebServiceProductRepository implements ProductRepository {//...
}
在启动应用程序之前,必须禁用数据库存储库,否则,您将获得有关接口的多个活动实现的异常。 返回第一个实现并应用以下更改:
@Repository
@ConditionalOnProperty(name = "feature.toggles.productsFromWebService",havingValue = "false",matchIfMissing = true
)
class DbProductRepository implements ProductRepository {
我们使用与以前相同的功能切换名称,只是其值已更改。 设置matchIfMissing属性是可选的。 这样,如果您从application.properties文件中删除功能切换,即使缺少该值,也将创建该bean。
3.如何通过功能切换禁用控制器
您可以应用相同的策略有条件地激活整个Spring Web控制器。 您不需要创建其他接口,因为您只想通过功能切换控制一个实现。
@RestController
@RequestMapping("/coupons")
@ConditionalOnProperty(name = "feature.toggles.coupons", havingValue = "true")
class CouponController {//...
}
application.properties应该包含以下行。
feature.toggles.coupons=true
当您不将值设置为true时,Spring不会实例化控制器。 客户端将仅收到404 HTTP状态代码。
不幸的是, @ ConditionalOnProperty批注不能在单个@RequestMapping方法上使用。 解决方法是,可以将所需的映射移动到单独的控制器Bean。 或者,可以简单地插入功能切换的值并在映射方法的主体中创建if语句。 但是,您应谨慎使用此解决方案。 如果您有兴趣,为什么在下一段中找到答案。
private final boolean couponsToggled;CouponController(@Value("${feature.toggles.coupons}") boolean couponsToggled) {this.couponsToggled = couponsToggled;
}@GetMapping
List<String> listCouponNames() {if (!couponsToggled) {throw new NotSupportedException();}//...
}
4.多功能切换管理
正如您可以阅读Martin Fowler的bliki上的功能切换一样 , 功能标志倾向于在整个代码库中扩展,并且很快就会变得难以管理 。 即使您的应用程序中只有几个功能切换,最好还是从使用标记的决策点抽象出标记的存储。
4.1避免特征标记耦合
上一段中的最后一个代码示例使用直接从application.properties文件注入的标志值,因此不会抽象存储。 如果要在应用程序的不同部分中使用相同的标志,则必须重复注入。
相反,您可以做的是将所有功能切换值放在一个类中,这将作为单个true来源 。 使用单独的类可为您提供更大的灵活性。 例如,您可以用数据库替换标志的存储,或者实现一种允许在运行时切换标志的机制。
4.2在Spring Boot中提取功能切换决策
一旦具有用于功能切换的单独的bean,就可以使用@ConfigurationProperties批注轻松地从application.properties文件中注入所有标志。 在这里,您可以看到一个示例实现:
@Component
@Component
@ConfigurationProperties("feature")
public class FeatureDecisions {private Map<String, Boolean> toggles = new HashMap<>();public Map<String, Boolean> getToggles() {return toggles;}public boolean couponEnabled() {return toggles.getOrDefault("coupons", false);}}
上面的类将获取所有以feature.toggles开头的属性,并将它们放入切换图。 如您所见,有一个名为couponEnabled()的方法,可用于从决策背后的逻辑中提取功能决策点。
此外,您还需要一个额外的依赖项才能启用对@ConfigurationProperties的处理。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
5.用于功能切换的执行器端点
由于您已经将所有功能切换都放在一个位置,因此现在要做的就是使用自定义的Actuator端点公开列表。 以下示例将向您展示如何进行。
@Component
@Endpoint(id = "feature-toggles")
class FeatureToggleInfoEndpoint {private final FeatureDecisions featureDecisions;FeatureToggleInfoEndpoint(FeatureDecisions featureDecisions) {this.featureDecisions = featureDecisions;}@ReadOperationpublic Map<String, Boolean> featureToggles() {return featureDecisions.getToggles();}}
如果您使用默认的Spring Boot 2 Actuator设置,则不会通过HTTP公开端点 。 为了在浏览器中对其进行测试,您必须通过将其标识符添加到application.properties文件中的Web include过滤器来启用Actuator端点。
management.endpoints.web.exposure.include=health,info,feature-toggles
运行应用程序后,请转到http:// localhost:8080 / actuator / feature-toggles查看端点返回的结果:
根据您的需求,您还可以在创建的端点上使用@WriteOperation实现在运行时切换功能切换的可能性。 本示例仅涵盖输出部分。
结论
在本文中,您可以了解Spring Boot应用程序中功能切换的实际示例。 我们从一个非常基本的示例开始,该示例涵盖了框架的所有需求。 之后,我们编写一些自定义代码来完成更多的自定义功能切换要求。 我们完成了有用的Actuator端点,以显示应用程序中所有功能标志的状态。
您可以在Github存储库中找到工作示例应用程序 。 如果您喜欢该帖子并认为它有用,请与您的关注者分享。 我也期待您在本文下面的问题和评论。
翻译自: https://www.javacodegeeks.com/2018/03/feature-toggle-in-spring-boot-2.html