Go SDK
本页面面向 Go 服务端工程师,介绍如何用 ABetterChoice Go SDK 在本地求值实验与 feature flag 并上报曝光。内容覆盖安装、初始化、三种求值 API 以及手动上报曝光。
支持平台
Golang SDK 兼容以下平台:
- Linux
- macOS
- Windows
开始使用
如果你还没有创建项目,请先按照 创建项目 的指引在 ABetterChoice 控制台创建项目,并按需创建实验。你可以暂时跳过这一步,但后续章节会用到项目 ID 以及实验所在的层名。
安装
Go SDK 通过 go get 安装,要求 Go 1.17 及以上:
go get github.com/abetterchoice/go-sdk初始化 SDK
安装完成后需要初始化 SDK。Init 接收三个参数:
- 第一个参数沿用 Go 惯例,传入
context。 - 第二个参数传入一个或多个项目 ID。
- 第三个参数类型为
InitOption,用于灵活配置初始化选项。当前最常用的是 secret key,可在控制台Settings → SDK&Key页面获取。
// Package main ...
package main
import (
"context"
abc "github.com/abetterchoice/go-sdk"
)
func main() {
abc.Init(context.Background(), []string{"{{.ProjectID}}"}, abc.WithSecretKey("{{.SecretKey}}"))
}[进阶:首次接入可以跳过本段,按需再回来阅读] InitOption 还提供更多初始化配置,完整列表见 InitOption 的实现。除了上面的 secret key,另一个常用选项是关闭曝光上报。默认情况下,调用 getExperiment API 时 ABetterChoice SDK 会自动上报一次曝光(即记录某个实验单元被该实验暴露过)。某些场景下这会产生大量非预期的曝光,可能稀释实验结果。为避免这种情况,可以在调用 getExperiment 时先不打曝光,等真正展示给用户时再补一次。设置 WithDisableReport() 将全局关闭曝光上报;我们也提供按 API 级别关闭曝光的方式,下文会展开。
abc.Init(context.Background(), []string{"{{.ProjectID}}"}, abc.WithSecretKey("{{.SecretKey}}"), abc.WithDisableReport(true))读取 feature flag
本节演示如何读取一个 feature flag 的值。如果还没创建,请先按 feature flag 文档 创建。假设在项目 project_id 下已经创建好名为 new_feature_flag、值类型为 Boolean 的 feature flag,可以这样读取:
abcUserContext := abc.NewUserContext("{{.UnitID}}")
featureFlag, err := abcUserContext.GetFeatureFlag(context.Background(), "project_id", "new_feature_flag")
if err == nil && featureFlag != nil {
flagValue := featureFlag.MustGetBool()
_ = flagValue
}获取实验并上报曝光
本节演示如何创建一个实验,并通过 SDK 提供的 API 拿到实验分组。
| 术语 | 含义 |
|---|---|
| UnitID | 实验单元 ID,可以是用户 ID、会话 ID 或设备 ID。ABetterChoice SDK 会保证同一个 unit ID 一直被分到同一组。 |
| Layer(层) | 层通常代表你产品里可用于实验的 100% 流量。一个层可以拆成一个或多个实验,同一层内的实验之间流量互斥。 |
| Experiment(实验) | 一个实验最多可以占满层内 100% 流量,内部进一步包含两个或多个实验组。 |
| Group(实验组) | 一个实验下的单元会被分到两个或多个组(例如 control / treatment)相互对照。 |
| Config(配置) | 绑在某个层上的取值。同一层下的多个实验可以共享相同的配置 key。 |
1. 创建一个新实验
如果还没有创建,请到控制台按 如何创建实验 的步骤创建一个新实验。
SDK 中用到的 layerKey 就是实验所在层的唯一标识,也叫 layerName,详见 层域说明。
2. 拉取实验分组
假设在第 1 步中,我们在项目 project_id 下创建了一个层名为 abc_layer_name 的实验,并在该层下定义了一个 Boolean 类型的配置 should_show_banner。请注意在最基础的形态下,层名默认等于实验名;但如果你在同一层下创建了多个实验,就需要到实验页面查看层名。SDK 提供了三种方式拉取某个 unit ID 的分组结果,三个 API 都要求先构造一个封装了该 unit ID 的用户上下文。
abcUserContext := abc.NewUserContext("{{.UnitID}}")a. 按层 key 拉取实验
第一种方式是按层 key 拉取实验分组,再通过强类型 API 读取层内的配置。当层 / 实验里有多个配置时这种写法更方便。即使同一层下有多个实验,这个 API 也会自动返回正确的结果——该 unit 必定且只能落入其中一个实验。还有一个好处是:在不改业务代码的前提下,你可以快速迭代实验——关掉旧实验、在同层下开新实验即可。
experiment, err := abcUserContext.GetExperiment(context.Background(), "project_id", "abc_layer_name")
if err == nil && experiment != nil {
shouldShowBanner := experiment.GetBoolWithDefault("should_show_banner", false)
_ = shouldShowBanner
}b. 按配置 key 拉取实验
这种方式和上面类似,唯一的区别是:不按层名读取,而是按配置名读取(同一项目下配置名全局唯一)。我们后续计划支持把一个配置从一个层迁到另一个层(例如 launcher 层),按这种方式接入未来无需改业务代码。
configResult, err := abcUserContext.GetValueByVariantKey(context.Background(), "project_id", "should_show_banner")
if err == nil && configResult != nil {
shouldShowBanner := configResult.GetBoolWithDefault(false)
}c. 拉取项目下全部实验分组
某些场景下你可能需要一次拿到同一项目下的全部实验分组。SDK 为此提供了批量 API,返回一个从层名到该 unit 在该层的分组结果的 map。如前所述,这样做容易导致曝光稀释,使用时务必小心;按需可通过 opts 关闭曝光上报,稍后再通过曝光 API 手动补打。
experiments, err := abcUserContext.GetExperiments(context.Background(), "project_id", abc.WithAutomatic(false))3. 上报曝光数据
按上面的方式拿到分组结果时,你可以选择先关闭曝光上报以避免稀释,再在合适的时机手动补打曝光:
abc.LogExperimentExposure(context.Background(), "{{.ProjectID}}", experiment)4. 完整示例
完整示例:
// Package main 是端到端的 ABetterChoice SDK 示例。
package main
import (
"context"
abc "github.com/abetterchoice/go-sdk"
"github.com/abetterchoice/go-sdk/env"
"github.com/abetterchoice/go-sdk/plugin/log"
)
func main() {
defer abc.Release()
projectID := "YOUR ProjectID"
// 非必需。如果你启用了 Private Service Connect 或私有化部署,
// 可以通过 env.RegisterAddr 指定后端地址,地址需以 http 或 https 开头。
err := env.RegisterAddr(env.TypePrd, "{{.BackendServiceAddress}}")
if err != nil {
log.Errorf("registerAddr fail:%v", err)
return
}
// 初始化 SDK,每个进程只需调用一次
err = abc.Init(context.Background(), []string{projectID}, abc.WithSecretKey("{{.SecretKey}}"))
if err != nil {
log.Errorf("Init fail:%v", err)
return
}
abcUserContext := abc.NewUserContext("{{.UnitID}}") // 稳定的用户标识
// 按配置 key 拉取实验,会直接返回该用户对应的配置值。
vr, err := abcUserContext.GetValueByVariantKey(context.Background(), projectID, "{{.ParamKey}}")
if err != nil || vr == nil {
log.Infof("GetValueByVariantKey fail:%v", err)
return
}
log.Infof("%d", vr.GetInt64WithDefault(0))
// 也可以按层 key 拉取实验。
experiment, err := abcUserContext.GetExperiment(context.Background(), projectID, "{{.Layer}}")
if err != nil || experiment == nil {
log.Errorf("GetExperiment fail:%v", err)
return
}
log.Infof("%v", experiment.GetBoolWithDefault("{{.ParamKey}}", false))
// 读取 feature flag
featureFlag, err := abcUserContext.GetFeatureFlag(context.Background(), projectID, "{{.FeatureKey}}")
if err != nil || featureFlag == nil {
log.Errorf("GetFeatureFlag fail:%v", err)
return
}
flagValue := featureFlag.MustGetBool()
log.Infof("the feature flag value is %v", flagValue)
}