我们平时在做任何项目时都少不了配置,Golang 项目也不例外。 在 Golang 的标准库中,并没有整套的配置方案,只有 flag 包来解析命令行参数和 os 包来获取当前环境变量等。 现在,我们来看一看一个 Golang 的第三方简易项目配置库,那就是 multiconfig

理想的项目配置方案

一个好的项目配置方案应该能用多种方式来配置选项,并且具有固定的优先顺序。

个人认为理想的顺序应该如下:

  1. 程序内应该为每个配置选项设定一个默认值,确保程序正确运行。
  2. 配置文件,例如toml、json、xml等,可以覆盖程序内配置,常用于不同开发、测试或正式环境的配置。
  3. 环境变量,可以覆盖前两种配置,可用于不同机器或运行环境。
  4. 运行命令时的参数,拥有最高优先级,能覆盖所有的配置,能完全个性化任何项目。

下面我们通过示例,实际体验 multiconfig 如何实现和操作这种优先顺序的。

使用和示例

首先要安装 multiconfig:

go get github.com/koding/multiconfig

安装完成后,让我们新建一个 config.go 文件,定义如下结构:

type Config struct {
    Name    string
    Port    int
    Enabled bool
    Users   []string
}

再添加载入代码:

func main()  {
    conf := new(Config)
    multiconfig.MustLoad(conf)
    fmt.Print(conf)
}

这时会输出:

&{ 0 false []}

都是类型的初始值。此外 multiconfig 还提供了两个结构体标签:requireddefault

如下,给 Port 添加 default 标签:

type Config struct {
    Name    string
    Port    int `default:"8080"`
    Enabled bool
    Users   []string
}

再次打印,输出如下:

&{ 8080 false []}

可以看到 Port 的值变为了 8080

我们再来给 Name 添加 required 属性:

type Config struct {
    Name    string `required:"true"`
    Port    int    `default:"8080"`
    Enabled bool
    Users   []string
}

再次运行,会出现 multiconfig: field 'Name' is required 错误,因为并未指定 Name 的值。

这时就需要引入更高优先级的配置了。 我们先来讲讲配置文件如何使用,在这里我选用了 toml 配置文件,它具有简短易读、语义精确等特点,就不详细展开了。

新建 local.toml 文件,写入如下配置:

Name="test"
Port=10000
Users=["A", "B"]

修改载入代码成如下:

func main() {
    conf := new(Config)
    multiconfig.MustLoadWithPath("local.toml", conf)
    fmt.Print(conf)
}

这时输出:

&{test 10000 false [A B]}

可以看到 Port8080 默认值被 10000 覆盖了,这正是我们期望的。

multiconfig 还提供了自动生成环境变量配置和命令行参数配置,可以通过添加 --help 选项并运行命令来查看:

./config --help

Usage of ./config:
  -enabled
        Change value of Enabled. (default false)
  -name
        Change value of Name. (default test)
  -port
        Change value of Port. (default 10000)
  -users
        Change value of Users. (default [A B])

Generated environment variables:
   CONFIG_ENABLED
   CONFIG_NAME
   CONFIG_PORT
   CONFIG_USERS

flag: help requested

这里再次验证了配置文件中的配置数据会覆盖程序内部的配置。

按照之前的描述,环境变量应该有着比配置文件更高的优先级,而命令行参数则拥有最高的优先级,我们来验证下。

运行 CONFIG_NAME="A" ./config 输出 &{A 10000 false [A B]}

运行 ./config --name="B" 输出 &{B 10000 false [A B]}

运行 CONFIG_NAME="A" ./config --name="B" 输出 &{B 10000 false [A B]}

这些和我们预期的完全一致。

总结

从上面的例子中,我们可以看到 multiconfig 只需要编写极少的代码就能实现多层次优先级别的配置, 从而保证程序在能正确运行前提下,实现自由灵活的配置。

在实际的项目开发过程中,尽量在代码层面提供可以运行的默认配置项。然后通过不同的配置文件,使不同用途的实例拥有各自的配置。 最后,在部署过程中,可以通过设置环境变量或命令行参数来做更精细的个性化,尤其在通过 supervisor 或 docker 部署时,显得格外方便。