知識庫 / Spring RSS 訂閱

Spring 中的特性開關

Architecture,Spring
HongKong
9
02:05 PM · Dec 06 ,2025

1. 概述

本文將簡要定義特徵標誌(feature flags)並提出一種在 Spring Boot 應用中實施它們的實用且具有主觀性的方法。然後,我們將深入研究更復雜的迭代方案,充分利用 Spring Boot 的不同功能。

我們將討論可能需要使用特徵標誌的各種場景,並探討可能的解決方案。我們將使用一個比特幣礦工示例應用程序來完成這些。

2. 特性標誌

特性標誌(有時被稱為功能開關)是一種機制,允許我們在不修改代碼或,理想情況下,無需重新部署應用程序的情況下,啓用或禁用應用程序的特定功能。

根據特定功能標誌的需求,我們可能需要全局配置它們、按應用程序實例配置,或者更精細地配置——例如,按用户或請求進行配置。

在軟件工程中的許多情況下,重要的是採用最直接的方法來解決問題,而無需添加不必要的複雜性。

特性標誌是一種強大的工具,當被明智地使用時,可以提高系統的可靠性和穩定性。然而,如果被濫用或未得到維護,它們會迅速成為複雜性和頭痛的來源。

以下是特性標誌可能派上用場的一些場景:

基於主幹的分支開發和非平凡的功能

在基於主幹的分支開發中,特別是當我們想要頻繁地集成更改時,我們可能會發現自己尚未準備好發佈某個功能的完整版本。特性標誌可以幫助我們繼續發佈,而無需使更改對所有用户可用。

環境特定的配置

我們可能會發現需要重置數據庫以進行端到端測試環境。

或者,我們可能需要使用與生產環境不同的安全配置,用於非生產環境。

因此,我們可以利用特性標誌來在正確的環境中啓用正確的設置。

A/B 測試

發佈解決相同問題但具有不同解決方案並衡量其影響的技術是一種我們可以使用特性標誌實施的引人入勝的技術。

小範圍發佈

在部署新功能時,我們可能會決定逐步進行,從一小部分用户開始,並在驗證其行為的正確性時逐步擴大采用。特性標誌使我們能夠實現這一點。

在後續部分中,我們將嘗試提供一種實用的方法來解決上述場景。

讓我們分解不同的特性標誌策略,從最簡單的場景開始,然後逐步過渡到更精細和更復雜的設置。

3. 應用層特性開關

如果需要解決前兩個用例中的任何問題,應用層特性開關提供了一種簡單的方法來快速實現功能。

一個簡單的特性開關通常涉及一個屬性和一個基於該屬性值的配置。

3.1. 使用 Spring Profiles 的特性標誌

在 Spring 中,我們可以利用 profiles。 方便地,profiles 允許我們選擇性地配置某些 Bean。 通過圍繞它們的一些構造,我們可以快速創建一個簡單而優雅的應用程序級別特性標誌解決方案。

假設我們正在構建一個 BitCoin 挖礦系統。 我們的軟件已經投入生產,我們被要求創建一個實驗性的、改進的挖礦算法。

在我們的 JavaConfig 中,我們可以對組件進行 profiling:

@Configuration
public class ProfiledMiningConfig {

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

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

然後,使用之前的配置,我們只需要將我們的配置文件包含進來,以便啓用我們的新功能。 有很多配置應用程序的方法, 尤其是在一般配置和啓用特定配置文件方面。 同樣,也有測試實用程序,以簡化我們的工作。

只要我們的系統足夠簡單,我們就可以創建一個基於環境的配置,以確定要應用哪些功能標誌以及哪些標誌應忽略。

假設我們有一個基於卡片的 UI,而不是表格,以及之前的實驗性礦工。

我們希望在驗收環境(UAT)中啓用這兩個功能。我們可以創建一個包含以下配置組的application.yml 文件:

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

在之前的配置就緒後,我們只需要在 UAT 環境中啓用 UAT 配置文件,就能獲得所需的全部功能。當然,我們也可以在我們的項目中添加 application-uat.yml 文件,以包含針對我們環境設置的額外屬性。

在我們的情況下,我們希望 uat 配置文件也包含 experimental-minerui-cards

注意:如果我們在使用 Spring Boot 版本低於 2.4.0,則我們會使用 spring.profiles.include 屬性,在 UAT 配置文件特定文檔中進行配置,以啓用額外的配置文件。與 spring.profiles.active 相比,前者允許我們以增量的方式包含配置文件。

3.2. 使用自定義屬性的特性標誌

配置文件是實現任務的便捷方式。然而,我們可能需要配置文件用於其他目的。或者,我們可能希望構建更完善的特性標誌基礎設施。

對於這些場景,自定義屬性可能是一種理想的選擇。

讓我們重寫我們之前的示例,利用 @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();
    }
}

上一個示例基於 Spring Boot 的條件配置,並根據屬性值是否設置為 truefalse (或未指定) 來配置不同的組件。

結果與 3.1 中的結果非常相似,但現在我們有了命名空間。擁有命名空間使我們能夠創建有意義的 YAML/屬性文件:

#[...] Some Spring config

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

此外,這種新的設置也允許我們為我們的特性標誌添加前綴——在我們的情況下,使用features前綴。

這可能看起來只是一個小的細節,但隨着我們的應用程序的增長和複雜性的增加,這種簡單的迭代將幫助我們控制特性標誌。

我們來談談這種方法的其他優勢。

3.3. 使用 @ConfigurationProperties

一旦我們獲得一組預定義的屬性,就可以創建一個用 @ConfigurationProperties 裝飾的POJO,以便在我們的代碼中獲得對這些屬性的程序化引用。

以下是我們在之前示例中的示例:

@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
    }
}

通過將功能標誌的狀態整合到一個統一的單元中,我們開闢了新的可能性,使其能夠輕鬆地向我們的系統中的其他部分暴露,例如 UI 或下游系統。

3.4. 功能配置暴露

我們的比特幣挖礦系統獲得了UI升級,但尚未完全就緒。因此,我們決定使用功能標誌(feature flagging)進行管理。我們可能會使用基於React、Angular或Vue的單頁應用。

無論採用何種技術,我們需要知道哪些功能已啓用,以便相應地渲染我們的頁面。

讓我們創建一個簡單的端點來提供我們的配置,以便我們的UI在需要時可以查詢後端:

@RestController
public class FeaturesConfigController {

    private ConfigProperties properties;

    // constructor

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

這種方式可能還有更高級的服務信息的方法,例如創建自定義 actuator 端點。但為了本指南的目的,控制器端點感覺已經足夠好一個解決方案。

3.5. 保持營地清潔

雖然這聽起來有些顯而易見,但一旦我們仔細實施了特徵標誌,保持在不再需要時及時清理這些標誌同樣重要。

對於首次使用情況——基於分支的開發和非瑣碎的特性,特徵標誌通常具有較短的生命週期。這意味着我們需要確保我們的 ConfigProperties(配置屬性)、Java 配置以及 YAML 文件保持清潔和最新。

4. 更精細的特性開關

有時我們會遇到更復雜的場景。對於 A/B 測試或青雀發佈,我們之前的做法根本不夠用。

為了實現更精細的特性開關,我們需要構建自己的解決方案。這可能包括自定義我們的用户實體以包含特性特定的信息,或者擴展我們的 Web 框架。

向用户注入大量的特性開關可能並不是所有人都喜歡的想法,但還有其他解決方案。

作為替代方案,我們可以利用一些內置工具,例如 Togglz。 這種工具會增加一些複雜性,但提供了一個開箱即用的解決方案,並且與 Spring Boot 提供一流的集成。

Togglz 支持不同的 激活策略

  1. 用户名: 與特定用户關聯的特性開關
  2. 逐步發佈: 特性開關為用户羣體的百分比啓用。 這對於青雀發佈非常有用,例如,當我們想要驗證我們功能的行為時
  3. 發佈日期: 可以安排特性開關在特定日期和時間啓用。 這可能對產品發佈、協調發布或優惠和折扣有幫助
  4. 客户端 IP: 基於客户端 IP 的特性開關。 這些在為具有靜態 IP 的特定客户應用特定配置時可能很有用
  5. 服務器 IP: 在這種情況下,服務器的 IP 用於確定特性是否應該啓用。 這對於青雀發佈也很有用,與逐步發佈略有不同——例如,當我們想要評估實例中的性能影響時
  6. ScriptEngine: 可以基於 任意腳本 啓用特性開關。 這是一個最具靈活性選項
  7. 系統屬性: 可以設置某些系統屬性以確定特性開關的狀態。 這與我們最直接的方法非常相似

5. 總結

在本文中,我們有機會探討了特徵標誌。此外,我們還討論了 Spring 如何幫助我們實現一些功能,而無需添加新的庫。

我們首先定義了這種模式如何幫助我們解決幾個常見用例。

接下來,我們使用 Spring 和 Spring Boot 內置工具構建了幾個簡單的解決方案。 這樣,我們提出了一個簡單而強大的特徵標誌構造。

下面,我們比較了幾個替代方案。 從更簡單、不太靈活的解決方案到更復雜但更高級的模式。

最後,我們簡要提供了構建更健壯解決方案的一些指南。 這在我們需要更高的粒度時非常有用。

發佈 評論

Some HTML is okay.