Feature Flags with Spring – 用Spring的特征标志

最后修改: 2018年 2月 23日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’ll briefly define feature flags and propose an opinionated and pragmatic approach to implement them in Spring Boot applications. Then, we’ll dig into more sophisticated iterations taking advantage of different Spring Boot features.

在这篇文章中,我们将简要地定义功能标志,并提出在Spring Boot应用程序中实现功能标志的观点和务实的方法。然后,我们将深入探讨利用不同的Spring Boot特性进行更复杂的迭代。

We’ll discuss various scenarios that might require feature flagging and talk about possible solutions. We’ll do this using a Bitcoin Miner example application.

我们将讨论可能需要功能标记的各种情况,并讨论可能的解决方案。我们将使用一个比特币矿工的例子应用程序来进行讨论。

2. Feature Flags

2.特征标志

Feature Flags – sometimes called feature toggles – are a mechanism that allows us to enable or disable specific functionality of our application without having to modify code or, ideally, redeploy our app.

功能标志–有时也称为功能切换–是一种机制,它允许我们启用或禁用我们应用程序的特定功能,而无需修改代码,或者最好是重新部署我们的应用程序。

Depending on the dynamics required by a given feature flag we might need to configure them globally, per app instance, or more granularly – perhaps per user or request.

根据一个特定的功能标志所要求的动态,我们可能需要在全局、每个应用实例或更细化地配置它们,也许是每个用户或请求。

As with many situations in Software Engineering, it’s important to try to use the most straightforward approach that tackles the problem at hand without adding unnecessary complexity.

就像软件工程中的许多情况一样,重要的是尝试使用最直接的方法来解决手头的问题,而不增加不必要的复杂性。

Feature flags are a potent tool that, when used wisely, can bring reliability and stability to our system. However, when they’re misused or under-maintained, they can quickly become sources of complexity and headaches.

功能标志是一种有效的工具,如果明智地使用,可以为我们的系统带来可靠性和稳定性。然而,当它们被误用或维护不足时,它们会迅速成为复杂和令人头痛的来源。

There are many scenarios where feature flags could come in handy:

有很多情况下,功能标志可以派上用场。

Trunk-based development and nontrivial features

基于主干的开发和非微不足道的功能

In trunk-based development, particularly when we want to keep integrating frequently, we might find ourselves not ready to release a certain piece of functionality. Feature flags can come in handy to enable us to keep releasing without making our changes available until complete.

在基于主干的开发中,特别是当我们希望经常保持集成时,我们可能会发现自己还没有准备好发布某项功能。特性标志可以让我们在不公开我们的改动的情况下继续发布,直到完成。

Environment-specific configuration

特定环境下的配置

We might find ourselves requiring certain functionality to reset our DB for an E2E testing environment.

我们可能会发现自己需要某些功能来重置我们的数据库,以实现E2E测试环境。

Alternatively, we might need to use a different security configuration for non-production environments from that used in the production environment.

另外,我们可能需要为非生产环境使用不同于生产环境的安全配置。

Hence, we could take advantage of feature flags to toggle the right setup in the right environment.

因此,我们可以利用功能标志的优势,在合适的环境中切换正确的设置。

A/B testing

A/B测试

Releasing multiple solutions for the same problem and measuring the impact is a compelling technique that we could implement using feature flags.

针对同一问题发布多种解决方案并测量其影响是一种引人注目的技术,我们可以使用特征标志来实现。

Canary releasing

加拿鱼的释放

When deploying new features, we might decide to do it gradually, starting with a small group of users, and expanding its adoption as we validate the correctness of its behavior. Feature flags allow us to achieve this.

在部署新功能时,我们可能会决定逐步进行,从一小群用户开始,在验证其行为的正确性后再扩大其采用。功能标志允许我们实现这一目标。

In the following sections, we’ll try to provide a practical approach to tackle the above-mentioned scenarios.

在下面的章节中,我们将尝试提供一个实用的方法来解决上述情况。

Let’s break down different strategies to feature flagging, starting with the simplest scenario to then move into a more granular and more complex setup.

让我们来分析一下特征标记的不同策略,从最简单的情况开始,然后进入更细化和更复杂的设置。

3. Application-Level Feature Flags

3.应用层面的功能标志

If we need to tackle any of the first two use cases, application-level features flags are a simple way of getting things working.

如果我们需要处理前两个用例中的任何一个,应用程序级的功能标志是一种简单的方式来实现工作。

A simple feature flag would typically involve a property and some configuration based on the value of that property.

一个简单的特征标志通常涉及一个属性和基于该属性值的一些配置。

3.1. Feature Flags Using Spring Profiles

3.1.使用Spring Profiles的特性标志

In Spring we can take advantage of profiles. Conveniently, profiles enable us to configure certain beans selectively. With a few constructs around them, we can quickly create a simple and elegant solution for application-level feature flags.

在Spring中,我们可以利用配置文件的优势。方便的是,配置文件使我们能够有选择地配置某些Bean。通过围绕它们的一些构造,我们可以快速创建一个简单而优雅的解决方案,以实现应用级的功能标志。

Let’s pretend we’re building a BitCoin mining system. Our software is already in production, and we’re tasked to create an experimental, improved mining algorithm.

让我们假设我们正在建立一个比特币挖矿系统。我们的软件已经在生产了,我们的任务是创造一个实验性的、改进的采矿算法。

In our JavaConfig we could profile our components:

在我们的JavaConfig中,我们可以对我们的组件进行分类。

@Configuration
public class ProfiledMiningConfig {

    @Bean
    @Profile("!experimental-miner")
    public BitcoinMiner defaultMiner() {
        return new DefaultBitcoinMiner();
    }

    @Bean
    @Profile("experimental-miner")
    public BitcoinMiner experimentalMiner() {
        return new ExperimentalBitcoinMiner();
    }
}

Then, with the previous configuration, we simply need to include our profile to opt-in for our new functionality. There’re tons of ways of configuring our app in general and enabling profiles in particular. Likewise, there are testing utilities to make our lives easier.

然后,使用之前的配置,我们只需包含我们的配置文件,以选择使用我们的新功能。大量的方法来配置我们的应用程序,特别是启用配置文件。同样,还有测试实用程序,使我们的生活更加轻松。

As long as our system is simple enough, we could then create an environment-based configuration to determine which features flags to apply and which ones to ignore.

只要我们的系统足够简单,我们就可以创建一个基于环境的配置,以决定应用哪些功能标志和忽略哪些标志。

Let’s imagine we have a new UI based on cards instead of tables, together with the previous experimental miner.

让我们想象一下,我们有一个基于卡片而不是表格的新UI,再加上之前的实验性矿工。

We’d like to enable both features in our acceptance environment (UAT). We could create the following profile group in our application.yml file:

我们想在我们的验收环境(UAT)中启用这两个功能。我们可以在我们的application.yml文件中创建以下配置文件组。

spring:
  profiles:
    group:
      uat: experimental-miner,ui-cards

With the previous property in place, we’d just need to enable the UAT profile in the UAT environment to get the desired set of features. Of course, we could also add an application-uat.yml file in our project to include additional properties for our environment setup.

有了前面的属性,我们只需要在UAT环境中启用UAT配置文件,就可以获得所需的功能集。当然,我们也可以在我们的项目中添加一个application-uat.yml文件,为我们的环境设置包含额外的属性。

In our case, we want the uat profile also to include experimental-miner and ui-cards.

在我们的案例中,我们希望uat配置文件也包括experimental-minerui-card.

Note: if we’re using a Spring Boot version prior to 2.4.0, we’d use the spring.profiles.include property in a UAT profile-specific document to configure the additional profiles. Compared to spring.profiles.active, the former enables us to include profiles in an additive manner.

注意:如果我们使用的是2.4.0之前的Spring Boot版本,我们会在UAT配置文件专用文档中使用spring.profiles.include属性来配置额外的配置文件。与spring.profiles.active相比,前者使我们能够以加法的方式包括配置文件。

3.2. Feature Flags Using Custom Properties

3.2.使用自定义属性的特征标志

Profiles are a great and simple way to get the job done. However, we might require profiles for other purposes. Or perhaps, we might want to build a more structured feature flag infrastructure.

轮廓是一种伟大而简单的方式,可以完成工作。然而,我们可能会因为其他目的而需要配置文件。或者,我们可能想建立一个更加结构化的特征标志基础设施。

For these scenarios, custom properties might be a desirable option.

对于这些情况,自定义属性可能是一个理想的选择。

Let’s rewrite our previous example taking advantage of @ConditionalOnProperty and our namespace:

让我们利用@ConditionalOnProperty和我们的命名空间重写我们之前的例子

@Configuration
public class CustomPropsMiningConfig {

    @Bean
    @ConditionalOnProperty(
      name = "features.miner.experimental", 
      matchIfMissing = true)
    public BitcoinMiner defaultMiner() {
        return new DefaultBitcoinMiner();
    }

    @Bean
    @ConditionalOnProperty(
      name = "features.miner.experimental")
    public BitcoinMiner experimentalMiner() {
        return new ExperimentalBitcoinMiner();
    }
}

The previous example builds on top of Spring Boot’s conditional configuration and configures one component or another, depending on whether the property is set to true or false (or omitted altogether).

前面的例子建立在Spring Boot的条件配置之上,并根据属性被设置为truefalse(或完全省略)来配置一个或另一个组件。

The result is very similar to the one in 3.1, but now, we have our namespace. Having our namespace allows us to create meaningful YAML/properties files:

其结果与3.1中的非常相似,但现在,我们有了我们的命名空间。有了我们的命名空间,我们就可以创建有意义的YAML/属性文件。

#[...] Some Spring config

features:
  miner:
    experimental: true
  ui:
    cards: true
    
#[...] Other feature flags

Also, this new setup allows us to prefix our feature flags – in our case, using the features prefix.

此外,这个新的设置允许我们对我们的特征标志进行前缀处理–在我们的例子中,使用features前缀.

It might seem like a small detail, but as our application grows and complexity increases, this simple iteration will help us keep our feature flags under control.

这可能看起来是一个小细节,但随着我们的应用程序的增长和复杂性的增加,这个简单的迭代将帮助我们保持我们的功能标志的控制。

Let’s talk about other benefits of this approach.

让我们来谈谈这种方法的其他好处。

3.3. Using @ConfigurationProperties

3.3.使用@ConfigurationProperties

As soon as we get a prefixed set of properties, we can create a POJO decorated with @ConfigurationProperties to get a programmatic handle in our code.

一旦我们得到一组前缀的属性,我们就可以创建一个用@ConfigurationProperties装饰的POJO来在我们的代码中获得一个程序化的处理。

Following our ongoing example:

按照我们正在进行的例子。

@Component
@ConfigurationProperties(prefix = "features")
public class ConfigProperties {

    private MinerProperties miner;
    private UIProperties ui;

    // standard getters and setters

    public static class MinerProperties {
        private boolean experimental;
        // standard getters and setters
    }

    public static class UIProperties {
        private boolean cards;
        // standard getters and setters
    }
}

By putting our feature flags’ state in a cohesive unit, we open up new possibilities, allowing us to easily expose that information to other parts of our system, such as the UI, or to downstream systems.

通过将我们的功能标志的状态放在一个有凝聚力的单元中,我们打开了新的可能性,使我们能够轻松地将该信息暴露给我们系统的其他部分,如用户界面,或下游系统。

3.4. Exposing Feature Configuration

3.4.暴露特征配置

Our Bitcoin mining system got a UI upgrade which is not entirely ready yet. For that reason, we decided to feature-flag it. We might have a single-page app using React, Angular, or Vue.

我们的比特币挖矿系统得到了一个用户界面的升级,还没有完全准备好。由于这个原因,我们决定对它进行功能标记。我们可能有一个使用React、Angular或Vue的单页应用程序。

Regardless of the technology, we need to know what features are enabled so that we can render our page accordingly.

无论采用何种技术,我们都需要知道哪些功能已经启用,以便我们能够相应地呈现我们的页面。

Let’s create a simple endpoint to serve our configuration so that our UI can query the backend when needed:

让我们创建一个简单的端点来服务于我们的配置,这样我们的用户界面就可以在需要时查询后台。

@RestController
public class FeaturesConfigController {

    private ConfigProperties properties;

    // constructor

    @GetMapping("/feature-flags")
    public ConfigProperties getProperties() {
        return properties;
    }
}

There might be more sophisticated ways of serving this information, such as creating custom actuator endpoints. But for the sake of this guide, a controller endpoint feels like good enough a solution.

可能有更复杂的方法来提供这些信息,例如创建自定义执行器端点。但就本指南而言,控制器端点感觉是一个足够好的解决方案。

3.5. Keeping the Camp Clean

3.5.保持营地清洁

Although it might sound obvious, once we’ve implemented our feature flags thoughtfully, it’s equally important to remain disciplined in getting rid of them once they’re no longer needed.

虽然这听起来很明显,但一旦我们深思熟虑地实施了我们的功能标志,一旦不再需要它们,保持纪律性地摆脱它们同样重要。

Feature flags for the first use case – trunk-based development and non-trivial features – are typically short-lived. This means that we’re going to need to make sure that our ConfigProperties, our Java configuration, and our YAML files stay clean and up-to-date.

第一个用例–基于主干的开发和非微不足道的功能–的功能标志通常是短暂的。这意味着我们需要确保我们的ConfigProperties、我们的Java配置和YAML文件保持干净和最新。

4. More Granular Feature Flags

4.更精细的功能标志

Sometimes we find ourselves in more complex scenarios. For A/B testing or canary releases, our previous approach is simply not enough.

有时我们发现自己处于更复杂的情况下。对于A/B测试或金丝雀发布,我们之前的方法根本不够用。

To get feature flags at a more granular level, we may need to create our solution. This could involve customizing our user entity to include feature-specific information, or perhaps extending our web framework.

为了在更细的层次上获得特征标志,我们可能需要创建我们的解决方案。这可能涉及到定制我们的用户实体以包括特定的特征信息,或者也许是扩展我们的网络框架。

Polluting our users with feature flags might not be an appealing idea for everybody, however, and there are other solutions.

然而,用功能标志来污染我们的用户可能不是一个吸引人的想法,还有其他的解决方案。

As an alternative, we could take advantage of some built-in tools such as Togglz. This tool adds some complexity but offers a nice out-of-the-box solution and provides first-class integration with Spring Boot.

作为替代方案,我们可以利用一些内置工具如Togglz。该工具增加了一些复杂性,但提供了一个很好的开箱即用的解决方案,并且提供了与Spring Boot的一流集成

Togglz supports different activation strategies:

Togglz支持不同的激活策略

  1. Username: Flags associated with specific users
  2. Gradual rollout: Flags enabled for a percentage of the user base. This is useful for Canary releases, for example, when we want to validate the behavior of our features
  3. Release date: We could schedule flags to be enabled at a certain date and time. This might be useful for a product launch, a coordinated release, or offers and discounts
  4. Client IP: Flagged features based on clients IPs. These might come in handy when applying the specific configuration to specific customers, given they have static IPs
  5. Server IP: In this case, the IP of the server is used to determine whether a feature should be enabled or not. This might be useful for canary releases too, with a slightly different approach than the gradual rollout – like when we want to assess performance impact in our instances
  6. ScriptEngine: We could enable feature flags based on arbitrary scripts. This is arguably the most flexible option
  7. System Properties: We could set certain system properties to determine the state of a feature flag. This would be quite similar to what we achieved with our most straightforward approach

5. Summary

5.总结

In this article, we had a chance to talk about feature flags. Additionally, we discussed how Spring could help us achieve some of this functionality without adding new libraries.

在这篇文章中,我们有机会讨论了功能标志。此外,我们还讨论了Spring如何在不添加新库的情况下帮助我们实现其中的一些功能。

We started by defining how this pattern can help us with a few common use cases.

我们首先定义了这种模式如何帮助我们处理一些常见的用例。

Next, we built a few simple solutions using Spring and Spring Boot out-of-the-box tools. With that, we came up with a simple yet powerful feature flagging construct.

接下来,我们使用Spring和Spring Boot的开箱即用工具建立了一些简单的解决方案。通过这些,我们想出了一个简单而强大的功能标记结构。

Down below, we compared a couple of alternatives. Moving from the simpler and less flexible solution to a more sophisticated, although more complex, pattern.

在下面,我们比较了几个备选方案。从更简单和不太灵活的解决方案转向更复杂的模式,虽然更复杂。

Finally, we briefly provided a few guidelines to build more robust solutions. This is useful when we need a higher degree of granularity.

最后,我们简要地提供了一些准则,以建立更强大的解决方案。当我们需要更高的颗粒度时,这很有用。