你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

Go 功能管理

Go 功能管理库提供了一种基于功能标志开发和公开应用程序功能的方法。 开发新功能后,许多应用程序都有特殊要求,例如应何时启用此功能以及在哪些条件下启用该功能。 此库提供了一种方法来定义这些关系,并集成到常见的 Go 代码模式中,使公开这些功能成为可能。

功能标志为 Go 应用程序提供动态打开或关闭功能的方法。 开发人员可以在简单的用例(如条件语句)中使用功能标志。

下面是使用 Go 功能管理库的一些优势:

  • 功能管理的常见约定
  • 低准入门槛
    • Azure 应用程序配置特性标志源
  • 功能标志生存期管理
    • 配置值可以实时更改
  • 涵盖从简单到复杂的场景
    • 打开/关闭功能
    • 基于对服务器的调用来动态评估功能的状态

Go 功能管理库是开源的。 有关详细信息,请访问 GitHub 存储库

功能标志

可以启用或禁用功能标志。 通过使用功能筛选器,可以将标志状态设为按条件变化。

功能筛选器

功能筛选器定义应启用功能的场景。 当评估某项功能是应打开还是关闭时,将遍历其功能筛选器列表,直到其中一个筛选器确定应启用该功能。 此时,该功能被视为已启用,并停止遍历功能筛选器。 如果没有功能筛选器指示应启用该功能,则它被视为已禁用。

例如,可以设计 Microsoft Edge 浏览器功能筛选器。 只要 HTTP 请求来自 Microsoft Edge,此功能筛选器就会激活所附加的任何功能。

功能标志配置

Go 功能管理支持通过内置的功能标志提供程序 azappconfig 来使用在 Azure 应用配置中定义的功能标志,同时也提供了一个通过 FeatureFlagProvider 接口的可扩展机制,以便使用其他提供程序定义的功能标志。

import (
    "context"
    "log"

    "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration"
    "github.com/microsoft/Featuremanagement-Go/featuremanagement"
    "github.com/microsoft/Featuremanagement-Go/featuremanagement/providers/azappconfig"
)


// ... ...
// Load Azure App Configuration
appConfig, err := azureappconfiguration.Load(ctx, authOptions, options)
if err != nil {
    log.Fatalf("Failed to load configuration: %v", err)
}


// Create feature flag provider
featureFlagProvider, err := azappconfig.NewFeatureFlagProvider(appConfig)
if err != nil {
    log.Fatalf("Error creating feature flag provider: %v", err)
}

// Create feature manager
featureManager, err := featuremanagement.NewFeatureManager(featureFlagProvider, nil)
if err != nil {
    log.Fatalf("Error creating feature manager: %v", err)
}

功能标志声明

以下示例显示了用于在 JSON 文件中设置功能标志的格式。

{
    "feature_management": {
        "feature_flags": [
            {
                "id": "FeatureT",
                "enabled": true
            },
            {
                "id": "FeatureU",
                "enabled": false
            },
            {
                "id": "FeatureV",
                "enabled": true,
                "conditions": {
                    "client_filters": [
                        {
                            "name": "Microsoft.TimeWindow",
                            "parameters": {
                                "Start": "Wed, 01 May 2019 13:59:59 GMT",
                                "End": "Mon, 01 Jul 2019 00:00:00 GMT"
                            }
                        }
                    ]
                }
            }
        ]
    }
}

按照约定,json 文档的 feature_management 部分用于加载功能标志设置。 功能标志对象必须在 feature_flags 部分下的 feature_management 数组中列出。

在上面的部分中,我们看到了三个不同的功能。 功能标志具有 idenabled 属性。 id 是用于标识和引用功能标志的名称。 enabled 属性指定功能标志的已启用状态。 如果 enabled 为 false,则该功能处于关闭状态。 如果 enabled 为 true,则特征的状态取决于条件。 如果没有条件,则该功能为 ON。 如果有条件,并且满足这些条件,则该功能为 ON。 如果有条件并且它们未满足,则该功能为 OFF。 conditions 属性声明用于动态启用该功能的条件。 功能在 client_filters 数组中定义其功能筛选器。 FeatureV 指定名为 Microsoft.TimeWindow 的功能筛选器。 此筛选器是可配置的功能筛选器的示例。 在该示例中可以看到筛选器具有 Parameters 属性。 此属性用于配置筛选器。 在本例中,配置功能激活的开始和结束时间。

可在feature_management找到 部分的详细架构。

高级:禁止在功能标志名称中使用冒号“:”。

启用/禁用声明

以下代码片段演示了为简单开/关功能定义功能的替代方法。

{
    "feature_management": {
        "feature_flags": [
            {
                "id": "FeatureT",
                "enabled": "true"
            },
            {
                "id": "FeatureX",
                "enabled": "false"
            }
        ]
    }
}

要求类型

功能标志的 requirement_type 属性用于确定筛选器在评估功能状态时应使用 Any 还是 All 逻辑。 如果未指定 requirement_type,则默认值为 Any

  • Any 表示只需一个筛选器评估为 true 即可启用该功能。
  • All 表示每个筛选器都需要评估为 true 才能启用该功能。

requirement_type 的一个 All 会更改遍历。 首先,如果没有筛选器,则该功能处于禁用状态。 然后,将遍历功能筛选器,直到其中一个筛选器确定应禁用该功能。 如果没有筛选器指示应禁用该功能,则它被视为已启用。

{
    "feature_management": {
        "feature_flags": [
            {
                "id": "FeatureW",
                "enabled": "true",
                "conditions": {
                    "requirement_type": "All",
                    "client_filters": [
                        {
                            "name": "Microsoft.TimeWindow",
                            "parameters": {
                                "Start": "Wed, 01 May 2019 13:59:59 GMT",
                                "End": "Mon, 01 Jul 2019 00:00:00 GMT"
                            }
                        },
                        {
                            "name": "Percentage",
                            "parameters": {
                                "Value": "50"
                            }
                        }
                    ]
                }
            },
        ]
    }
}

在上面的示例中,FeatureW 指定 requirement_typeAll,这意味着所有筛选器都必须评估为 true 才能启用该功能。 在本例中,在指定时间范围内为 50% 的用户启用了该功能。

消耗

功能管理的基本形式是检查是否启用了功能标志,然后根据结果执行操作。 通过 FeatureManagerIsEnabled 方法检查功能标志的状态。

// Create feature flag provider
featureFlagProvider, err := azappconfig.NewFeatureFlagProvider(appConfig)
if err != nil {
    log.Fatalf("Error creating feature flag provider: %v", err)
}

// Create feature manager
featureManager, err := featuremanagement.NewFeatureManager(featureFlagProvider, nil)
if err != nil {
    log.Fatalf("Error creating feature manager: %v", err)
}

// Check if feature is enabled
enabled, err := featureManager.IsEnabled("FeatureX")
if err != nil {
    log.Printf("Error checking feature: %v", err)
    return
}

if enabled {
    // Do something
}

实现功能筛选器

创建功能筛选器提供了一种基于所定义条件启用功能的方法。 若要实现功能筛选器,必须实现 FeatureFilter 接口。 FeatureFilter 有一个方法名为 Evaluate。 当某个功能与筛选器关联时,在评估过程中会调用 Evaluate 方法。 如果 Evaluate 返回 true,则认为该功能已启用。

type FeatureFilter interface {
	// Name returns the identifier for this filter
	Name() string

	// Evaluate determines whether a feature should be enabled based on the provided contexts
	Evaluate(evalCtx FeatureFilterEvaluationContext, appCtx any) (bool, error)
}

type FeatureFilterEvaluationContext struct {
	// FeatureName is the name of the feature being evaluated
	FeatureName string

	// Parameters contains the filter-specific configuration parameters
	Parameters map[string]any
}

以下代码片段演示如何实现自定义功能筛选器。

type MyCustomFilter struct{}

func (f *MyCustomFilter) Evaluate(ctx context.Context, context *FeatureFilterEvaluationContext) bool {
    // Custom logic to determine if feature should be enabled
    if satisfyCriteria() {
        return true
    }
    return false
}

func (f *MyCustomFilter) Name() string {
    return "MyCustomFilter"
}

创建 FeatureManager 时,向功能筛选器提供上述代码以注册功能筛选器。 如果自定义功能筛选器需要任何上下文,则可以通过 FeatureFilterEvaluationContext 参数传入它们。

// Register custom filters
options := &featuremanagement.Options{
    Filters: []featuremanagement.FeatureFilter{
        &MyCustomFilter{},
    },
}

// Create feature manager with custom filters
featureManager, err := featuremanagement.NewFeatureManager(featureFlagProvider, options)
if err != nil {
    log.Fatalf("Error creating feature manager: %v", err)
}

筛选器别名属性

为功能标志注册功能筛选器时,其名称将用作默认别名。 可以通过实现 Name() 方法来替代此标识符,该方法指定在引用功能标志中的筛选器时在配置中使用的名称。

缺少功能筛选器

如果将某个功能配置为为特定功能筛选器启用,并且该功能筛选器未注册,则会在评估该功能时返回错误。

内置功能筛选器

featuremanagement软件包附带两个功能筛选器:TimeWindowFilterTargetingFilter

每个内置功能筛选器都有自己的参数。 以下是功能筛选器的列表和示例。

Microsoft.TimeWindow

此筛选器提供根据时间窗口来启用某项功能的功能。 如果仅指定了 End,则在此时间之前该功能被视为已启用。 如果仅指定了 Start,则在该时间之后的所有时间点,该功能被视为已启用。

"client_filters": [
    {
        "name": "Microsoft.TimeWindow",
        "parameters": {
            "Start": "Wed, 01 May 2019 13:59:59 GMT",
            "End": "Mon, 01 Jul 2019 00:00:00 GMT"
        }
    }
]     

Microsoft.Targeting

此筛选器提供为目标受众启用某项功能的功能。 以下定位部分对此进行了深入说明。 筛选器参数包括一个 Audience 对象,该对象描述用户、组、已排除的用户/组,以及应该有权访问该功能的用户群的默认百分比。 Groups 部分中列出的每个组对象还必须指定应具有访问权限的组成员的百分比。 如果直接在 Exclusion 部分中指定了用户,或者该用户在排除的组中,则该功能将被禁用。 否则,如果直接在 Users 部分中指定了用户,或者如果用户处于任何组推出包含的百分比中,或者处于默认推出百分比中,则此用户将启用该功能。

"client_filters": [
    {
        "name": "Microsoft.Targeting",
        "parameters": {
            "Audience": {
                "Users": [
                    "Jeff",
                    "Alicia"
                ],
                "Groups": [
                    {
                        "Name": "Ring0",
                        "RolloutPercentage": 100
                    },
                    {
                        "Name": "Ring1",
                        "RolloutPercentage": 50
                    }
                ],
                "DefaultRolloutPercentage": 20,
                "Exclusion": {
                    "Users": [
                        "Ross"
                    ],
                    "Groups": [
                        "Ring2"
                    ]
                }
            }
        }
    }
]

Targeting

目标定位是一种功能管理策略,使开发人员能够逐步向其用户群推出新功能。 该策略建立在面向一组称为目标受众的用户的概念之上。 受众由特定用户、组、已排除的用户/组和占整个用户群的指定百分比的人数组成。 受众中包含的组可以进一步细分为其成员总数的百分比。

以下步骤演示了新“Beta 版”功能的渐进式推出示例:

  1. 个人用户 Jeff 和 Alicia 有权访问 Beta 版。
  2. 另一位用户 Mark 请求加入并被接纳。
  3. 一组称为“Ring1”的用户中有 20% 被包含在 Beta 版中。
  4. Beta 版中包含的“Ring1”用户数量增加到 100%。
  5. Beta 版中包含 5% 的用户群。
  6. 推出百分比将提升到 100%,该功能已完全推出。

此功能推出策略通过随附的 Microsoft.Targeting 功能筛选器内置到库中。

定位用户

目标筛选器依赖于目标上下文来评估是否应启用某项功能。 此目标上下文包含的信息,包括当前正在评估的特定用户,以及用户所属的群组。 调用 IsEnabledWithAppContext 时,必须直接传递目标上下文。

// ... ...
// Create targeting context
targetingCtx := featuremanagement.TargetingContext{
    UserID: "test_user",
    Groups: []string{"Ring1"},
}

// Check if feature is enabled for the user
enabled, err := featureManager.IsEnabledWithAppContext("Beta", targetingCtx)
if err != nil {
    log.Printf("Error checking feature: %v", err)
    return
}

if enabled {
    // Feature is enabled for this user
}

目标排除

定义受众时,可以从该受众中排除用户和组。 如果要向一组用户推出某项功能,但需要从推出中排除一些用户或组,则排除功能非常有用。 通过将用户和组列表添加到受众的 Exclusion 属性来定义排除。

"Audience": {
    "Users": [
        "Jeff",
        "Alicia"
    ],
    "Groups": [
        {
            "Name": "Ring0",
            "RolloutPercentage": 100
        }
    ],
    "DefaultRolloutPercentage": 0,
    "Exclusion": {
        "Users": [
            "Mark"
        ]
    }
}

在上面的示例中,为名为 JeffAlicia 的用户启用功能。 它还为名为 Ring0 的组中的用户启用该功能。 但是,如果用户命名为 Mark,则会禁用该功能,而不考虑其是否位于组 Ring0 中。 排除项优先于目标定位过滤器的其他部分。

变量

将新功能添加到应用程序时,有时某项功能具有多个不同的建议设计选项。 决定设计的常见解决方案是某种形式的 A/B 测试。 A/B 测试涉及向用户群的不同段提供不同版本的功能,并根据用户交互选择版本。 在此库中,通过用变体表示功能的不同配置来启用此功能。

变体使功能标志变得不仅仅是一个简单的开/关标志。 变体表示功能标志的值,可以是字符串、数字、布尔值,甚至是配置对象。 用于声明变体的功能标志应定义每个变体的使用条件,这将在分配变体部分中进行更详细的介绍。

type Variant struct {
	// Name uniquely identifies this variant
	Name string

	// ConfigurationValue holds the value for this variant
	ConfigurationValue any
}

获取变体

对于每个功能,可以使用 FeatureManagerGetVariant 方法检索变体。 变体分配取决于当前正在评估的用户,并且该信息是从您提供的定位上下文中获取的。

targetingCtx := featuremanagement.TargetingContext{
    UserID: "Adam",
}

variant, err := featureManager.GetVariant("TestVariants", targetingCtx)
if err != nil {
    log.Printf("Error getting variant: %v", err)
    return
}

if variant != nil {
    variantConfiguration := variant.Configuration

    // Do something with the resulting variant and its configuration
}

变种功能标志声明

与普通功能标志相比,变体功能标志具有两个附加属性:variantsallocationvariants 属性是一个数组,其中包含为此功能定义的变体。 allocation 属性定义为该功能分配这些变体的方式。 与声明普通功能标志一样,可以在 JSON 文件中设置变体功能标志。 以下是变体功能标志的示例。

{
    "feature_management": {
        "feature_flags": [
            {
                "id": "MyVariantFeatureFlag",
                "enabled": true,
                "allocation": {
                    "default_when_enabled": "Small",
                    "group": [
                        {
                            "variant": "Big",
                            "groups": [
                                "Ring1"
                            ]
                        }
                    ]
                },
                "variants": [
                    { 
                        "name": "Big"
                    },  
                    { 
                        "name": "Small"
                    } 
                ]
            }
        ]
    }
}

定义变体

每个变体都有两个属性:名称和配置。 名称用于引用特定变体,而配置则是该变体的值。 可以使用 configuration_value 属性设置配置。 configuration_value 是一个内联配置,它可以是字符串、数字、布尔值或配置对象。 如果未指定 configuration_value,则返回的变体的 Configuration 属性将为 nil

已为 variants 属性下的每个功能定义所有可能的变体的列表。

{
    "feature_management": {
        "feature_flags": [
            {
                "id": "MyVariantFeatureFlag",
                "variants": [
                    { 
                        "name": "Big", 
                        "configuration_value": {
                            "Size": 500
                        }
                    },  
                    { 
                        "name": "Small", 
                        "configuration_value": {
                            "Size": 300
                        }
                    } 
                ]
            }
        ]
    }
}

分配变体

分配功能变体的过程由该功能的 allocation 属性确定。

"allocation": { 
    "default_when_enabled": "Small", 
    "default_when_disabled": "Small",  
    "user": [ 
        { 
            "variant": "Big", 
            "users": [ 
                "Marsha" 
            ] 
        } 
    ], 
    "group": [ 
        { 
            "variant": "Big", 
            "groups": [ 
                "Ring1" 
            ] 
        } 
    ],
    "percentile": [ 
        { 
            "variant": "Big", 
            "from": 0, 
            "to": 10 
        } 
    ], 
    "seed": "13973240" 
},
"variants": [
    { 
        "name": "Big", 
        "configuration_value": "500px"
    },  
    { 
        "name": "Small", 
        "configuration_value": "300px"
    } 
]

功能的 allocation 设置具有以下属性:

资产 Description
default_when_disabled 指定在该功能被视为已禁用的情况下请求变体时应使用哪个变体。
default_when_enabled 指定在以下情况下请求变体时应使用哪个变体,即如果该功能被视为已启用,并且没有向用户分配其他变体。
user 指定变体以及应将该变体分配到的用户列表。
group 指定变量和组列表。 如果用户至少位于其中一个组,则会分配该变体。
percentile 指定一个变体和一个百分比范围,计算出的用户百分比必须处于此范围内才能分配该变体。
seed percentile 的百分比计算所基于的值。 如果使用相同的 seed 值,则特定用户的百分比计算在所有功能中都是相同的。 如果未指定 seed,则会根据功能名称创建默认种子。

如果未启用该功能,则功能管理器会将标记为 default_when_disabled 的变体分配给当前用户,在本例中为 Small

如果已启用该功能,则功能管理器将按该顺序检查 usergrouppercentile 分配,以分配变体。 对于此特定示例,如果接受评估的用户在名为 Marsha 的组中命名为 Ring1,或者该用户恰好位于 0 和第 10 个百分位数之间,则会将指定的变体分配给该用户。 在这种情况下,所有分配的用户都会返回 Big 变体。 如果这些分配都不匹配,则会为用户分配 default_when_enabled 变体,它是 Small

分配逻辑类似于 Microsoft.Targeting 功能筛选器,但有一些存在于目标中的参数不在分配中,反之亦然。 目标和分配的结果不相关。

使用变体替代已启用状态

可以使用变体来替代功能标志的已启用状态。 替代使变体有机会扩展功能标志的评估。 使用变体对标志调用 IsEnabledWithAppContext 时,功能管理器将检查分配给当前用户的变体是否已配置为替代结果。 替代是使用可选变量属性 status_override 完成的。 默认情况下,此属性设置为 None,这意味着变体不会影响标志是被视为已启用还是已禁用。 将 status_override 设置为 Enabled 允许变体(选中时)替代要启用的标志。 将 status_override 设置为 Disabled 会提供相反的功能,因此在选择变体时会禁用标志。 enabled 状态为 false 的功能无法被替代。

如果使用具有二进制变体的功能标志,则 status_override 属性非常有用。 它可让你继续在应用程序中使用 IsEnabledaWithAppContext 等 API,同时受益于变体附带的新功能,例如百分位数分配和种子。

{
    "id": "MyVariantFeatureFlag",
    "enabled": true,
    "allocation": {
        "percentile": [
            {
                "variant": "On",
                "from": 10,
                "to": 20
            }
        ],
        "default_when_enabled":  "Off",
        "seed": "Enhanced-Feature-Group"
    },
    "variants": [
        {
            "name": "On"
        },
        {
            "name": "Off",
            "status_override": "Disabled"
        }
    ]
}

在上面的示例中,始终启用该功能。 如果当前用户在计算出的第 10 到第 20 百分位数范围内,则会返回 On 变体。 否则,将返回 Off 变体,因为 status_override 等于 Disabled,因此该功能现在被视为已禁用。

后续步骤

若要了解如何在应用程序中使用功能标志,请继续阅读以下快速入门。

若要了解如何使用功能筛选器的信息,请继续学习以下教程。