# Options Store
Option Store, 或者 Options Store,管理着一个应用程序的全部配置数据。 这些数据可能来自于环境变量、命令行输入、配置文件,甚至是从外部配置中心装载。它们可以有任意的层级和嵌套关系。它们可以包含布尔量,整数,浮点数,字符串,数组,Map 等多种格式的值。
# 基本定义
# keyPath
keyPath
是 Option Store
中的专属概念,一个选项能够被通过 前缀.子命令序列.标志长格式
的方式被访问,这个格式就被称作 keyPath
。
例如 app.cert.create.cacert
中,app 是内置的前缀,cert.create 代表着 cert 命令及其 create 子命令,cacert 除了是标志的长格式字串之外,也表达出了从属于 cert.create 子命令的含义。
# 选项(Option)和标志(Flag)的关系
一个标志(Flag),指的是通过 cmdr.NewBool()
等接口函数定义的实体,这个实体被用于命令行参数的解释,对应着一个特定的命令行参数选项。
一个选项(Option),指的是储存在 Option Store
中的一个条目,例如 “app.debug” => true
。
一个选项可能和一个标志相关联,但也可能不。但一个标志一定会对应着一个选项。
对于 app.debug
这个选项来说,由于 app
是内部设定的选项前缀(所以在体现到标志时不予考虑),故顶级标志 --debug
与其是关联的。它的 yaml 表示能够体现出层级关系:
app:
debug: true
2
而对于多级子命令的标志,例如 子命令 cert / create
的标志 --cacert
,会对应着 app.cert.create.cacert
这个选项。它的 yaml 表示如同这样:
app:
cert:
create:
cacert: ca.pem
cert: cert.pem
key: cert.key
2
3
4
5
6
# GetXXX
当使用 GetXXX
来获取选项值时,R版本能够忽略 app
前缀的指定,从而简化你的编程思路。也就是说,GetBoolR("debug")
将会取得 app.debug
的布尔量值。
一般来说,我们支持如下几种 GetXXX 的变体:
cmdr.GetBool("app.server.start.debug", false) == true
cmdr.GetBoolR("server.start.debug", false) == true
cmdr.GetBoolP("app.server.start", "debug", false) == true
cmdr.GetBoolRP("server.start", "debug", false) == true
2
3
4
根据你的上下文代码的便利性,你可以选取最恰当的一种变体。
在多数情况下,为了代码逻辑的清晰性,或者 R 变体版本是最佳的选择。
# app
前缀
app
前缀是为了在序列化 Option Store 为 YAML 或者其他外部格式时而特别建立的前缀,这样能够保证 Option Store 的序列化内容能够被恰当地包含到外部配置中心里(无论是 YAML,JSON,TOML,抑或是显式的微服务外部配置中心)。
cmdr.WithOptionsPrefix(prefixes...)
允许你定制前缀,你可以不必使用内置的 app
前缀而是使用 voxr.app-gateway.
作为前缀:
func main() {
if err := cmdr.Exec(buildRootCmd(),
cmdr.WithOptionsPrefix("voxr", "app-gateway"),
); err != nil {
fmt.Printf("error: %+v\n", err)
}
}
2
3
4
5
6
7
而对于同一个系统 voxr 的 api 服务,则可以使用 voxr.api.
作为前缀:
cmdr.WithOptionsPrefix("voxr", "api"),
# 构造过程
Option Store 包含了一整颗键值对的树结构。
这棵树是在 cmdr.Exec() 的初始化部分被构造的。
# 来源
Option Store 中的配置数据来自这些地方:
通过
cmdr.NewBool
,cmdr.NewString
等接口定义的命令行标志信息中提供的缺省值。例如:cmdr.NewBool(false). Titles("enable-ueh", "ueh"). EnvKeys("ENABLE_UEH"). Description("Enables the unhandled exception handler?"). AttachTo(root)
1
2
3
4
5这里定义了一个 Bool 类型的标志(Flag),其默认值为 false,如果终端用户没有作出指定,则 Option Store 中会包含该条目且具有 bool 值 false。你可以通过
cmdr.GetBoolR('enable-ueh')
取得该值。标志总是带有默认值 false,一般较少会使用 true值。
Toggleable Flags Group 例外,通常在组里会有一个 Flag 具有默认值 true。
通过命令行参数指定的。例如:
$ go run ./cli --enable-ueh
1这里会通过命令行将 enable-ueh 的值设置为 true。
此时,通过
cmdr.GetBoolR('enable-ueh')
取得的值将会为 true。通过环境变量指定的。例如:
ENABLE_UEH=1 go run /cli
1如果在定义 Flag 是没有通过
.EnvKeys()
指定环境变量名,cmdr
会试图查找自动化命名的环境变量名。例如此例中,自动化的环境变量名应该是APP_ENABLE_UEH
,请参考cmdr.WithEnvPrefix()
的相关说明。通过配置文件装入的。例如在主配置文件中包含有如下条目:
app: # 这是配置文件的前缀,可以通过 cmdr.WithOptionsPrefix() 自定义 simple: # 这是通过 cmdr.Root(appName, version) 所指定的应用程序名 enable-ueh: true # 字段名称应该等于 Flag 的 Full 字段值
1
2
3
4通过
cmdr.Set(keyPath, value)
设置的。
# 构造 Xref 索引
在从来源构建键值对的树状结构的同时,cmdr 也会建立内部使用的索引表。这些索引表有多种用途,其中之一是完善用户定义的数据结构,将各命令、标志以及彼此之间的关联关系构造完整。
所以对于 Developers 来说,你可以较为宽松地定义命令或标志,不必太多操心各种细节。
例如你可以定义 ToggleGroup("fruits")
,但可以略过 Group("fruits")
定义,因为在 Xref 构造期间这样的缺省关联关系会被自动补全。
# 外部配置文件
cmdr 自动查找一系列预定义的路径中符合要求的主配置文件(其文件名应该等于应用程序名,并且带有 .yml|yaml|json|toml 后缀),并自动载入其中的参数定义,同时监视该文件所在位置的名为 conf.d
的子目录中的任何配置文件,只要它们带有 .yml|yaml|json|toml 后缀 。
# 控制对配置文件的监视策略
默认时,cmdr 不会监视主配置文件,但 WithWatchMainConfigFileToo (opens new window) 能够改变这一行为。
func WithNoWatchConfigFiles(b bool) (opens new window) 可以关闭 cmdr 对配置文件或/及其子目录的监视。
func WithNoLoadConfigFiles(b bool) (opens new window) 则完全跳过载入配置文件的环节。
# 预定义位置
默认时,预定位置为如下的数组:
[]string{
"./ci/etc/%s/%s.yml", // for developer
"/etc/%s/%s.yml", // regular location
"/usr/local/etc/%s/%s.yml", // regular macOS HomeBrew location
"$HOME/.config/%s/%s.yml", // per user
"$HOME/.%s/%s.yml", // ext location per user
"$THIS/%s.yml", // executable's directory
"%s.yml", // current directory
},
2
3
4
5
6
7
8
9
其中的 $HOME
,$THIS
会通过环境变量展开,$THIS
表示执行文件所在的目录。%s
会被展开为 应用程序名。
通过 WithPredefinedLocations (opens new window) 你可以定制这个数组,从而自定义 cmdr 如何去寻找主配置文件,包括当你想要主配置文件不一定采用应用程序名称时,也能从此处进行改变。
# 更新后处理
如果你愿意提供对外部依赖资源(例如 redis 服务)的动态重新初始化的能力的话,通过 WithConfigLoadedListener (opens new window) 可以实现这样的特性,当然具体实施依然会是很复杂的,你需要有正确的解决数据竞争的代码逻辑。
# Handlers
# WithXrefBuildingHooks (opens new window)
WithXrefBuildingHooks
可以在 cmdr 的 Xref 索引构造前后设置拦截器。
一般的说,后置拦截器是一个很有用的拦截点。因为在这个时候,所有常规途径的命令行参数定义都被预处理过了,所有环境变量值都已经被关联到对应的选项了,所有的外部配置文件中的值都已经被解释并构造到 Option Store 中了。所以此时如果你要追加针对整棵树的操作的话,这里就是最佳的切入点。
# WithAfterArgsParsed (opens new window)
cmdr 在 Xref 索引构建完成之后会开始解释命令行参数。而当命令行参数被有效地解释之后,WithAfterArgsParsed (opens new window) 所定义的回调函数将获得执行机会,如果回调函数无错地返回,则 cmdr 将会转去执行已经命中的 子命令的响应函数(Action),否则的话 cmdr 放弃进一步操作退出应用程序。
所以这里可以做全局的身份鉴定,参数有效性验证等操作——根据你的实际需要。
# WithConfigLoadedListener (opens new window)
在 cmdr 执行命中子命令的 Action 函数的过程中,外部配置文件也处于被监视的状态中。如果外部配置文件发生了变化,那么对应的文件将被自动载入并合并到 Option Store
的现有参数树中,然后 WithConfigLoadedListener (opens new window) 所指定的回调函数将会获得一个阐述这些变更的机会。
如果你愿意提供对外部依赖资源(例如 redis 服务)的动态重新初始化的能力的话,通过 WithConfigLoadedListener (opens new window) 可以实现这样的特性,当然具体实施依然会是很复杂的,你需要有正确的解决数据竞争的代码逻辑。
# WithAutomaticEnvHooks (opens new window)
在外部配置文件被解释之后,cmdr 的初始化代码也会扫描环境变量并完成映射。
在完成了匹配的环境变量的映射操作之后, WithAutomaticEnvHooks (opens new window) 所指定的回调函数会获得执行机会。你可以借助这个回调函数对默认的映射结果进行审视。
🔚