Skip to content

Go SDK

本页面面向 Go 服务端工程师,介绍如何用 ABetterChoice Go SDK 在本地求值实验与 feature flag 并上报曝光。内容覆盖安装、初始化、三种求值 API 以及手动上报曝光。

支持平台

Golang SDK 兼容以下平台:

  • Linux
  • macOS
  • Windows

开始使用

如果你还没有创建项目,请先按照 创建项目 的指引在 ABetterChoice 控制台创建项目,并按需创建实验。你可以暂时跳过这一步,但后续章节会用到项目 ID 以及实验所在的层名。

安装

Go SDK 通过 go get 安装,要求 Go 1.17 及以上:

bash
go get github.com/abetterchoice/go-sdk

初始化 SDK

安装完成后需要初始化 SDK。Init 接收三个参数:

  1. 第一个参数沿用 Go 惯例,传入 context
  2. 第二个参数传入一个或多个项目 ID。
  3. 第三个参数类型为 InitOption,用于灵活配置初始化选项。当前最常用的是 secret key,可在控制台 Settings → SDK&Key 页面获取。
go
// 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 级别关闭曝光的方式,下文会展开。

go
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,可以这样读取:

go
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 的用户上下文。

go
abcUserContext := abc.NewUserContext("{{.UnitID}}")

a. 按层 key 拉取实验

第一种方式是按层 key 拉取实验分组,再通过强类型 API 读取层内的配置。当层 / 实验里有多个配置时这种写法更方便。即使同一层下有多个实验,这个 API 也会自动返回正确的结果——该 unit 必定且只能落入其中一个实验。还有一个好处是:在不改业务代码的前提下,你可以快速迭代实验——关掉旧实验、在同层下开新实验即可。

go
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 层),按这种方式接入未来无需改业务代码。

go
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 手动补打。

go
experiments, err := abcUserContext.GetExperiments(context.Background(), "project_id", abc.WithAutomatic(false))

3. 上报曝光数据

按上面的方式拿到分组结果时,你可以选择先关闭曝光上报以避免稀释,再在合适的时机手动补打曝光:

go
abc.LogExperimentExposure(context.Background(), "{{.ProjectID}}", experiment)

4. 完整示例

完整示例:

go
// 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)
}