golang-envutils

「Golang 反射实战」 - 我用 Golang 反射写了一个配置库 - envutils , 应用再也不会少变量了

原文链接: https://tangx.in/posts/2023/01/27/golang-envutils-config/

用习惯了 struct 之后, 我想所有东西都通过 struct 管理。 学习了反射之后, 我总要找点事情做来练习。

于是我整合了 Golang环境变量操作Golang反射, 以及解决了我认为的其他一些配置管理的痛点, 便有了这个项目。

  1. 一篇文章告诉你 golang 环境变量的所有基础操作
  2. 失败99次之后, 我总记了

Github 仓库: https://github.com/tangx/envutils

配置管理的痛点

  1. 配置来源多样性: 这个痛点主要来源于容器, 以前容器配置 CofnigMap 或者 Secret 挂载文件还是很麻烦的, 远不如直接使用 环境变量 方便。
  2. 数据映射: 使用环境变量又带来了新的问题, 通常在使用的时候, 我习惯把所有变量写在一个 结构体struct 中, 但是如何把 环境变量名称配置结构体 关联起来?
  3. 变量的增减管理: 随着项目的不断演进变量可能 增加或者删除 , 要如何在一个 醒目/固定 的位置留档? 或者如何每次程序都能导出当前版本的所有配置需求?
  4. 实现多配置叠加管理: 这个痛点来自于 CICD 发布的测试环境, 如何使不同的 feature 分支能使用自己的独立的配置, 合并的时候又不影响其他人。

以及一些其他的小地方

于是, 为了解决以上几个痛点, 我自己造了一个轮子。 这个轮子支持

  1. 配置结构体 转成一个 有规则的keymap, 以保存到文件中
  2. 通过读取 配置文件 或者 环境变量 重新将值 映射配置结构体 中。
  3. 支持 应用名称前缀 , 方便在多应用环境区分。
  4. 支持 默认值 , 减少多配置文件是的工作量。

实现效果

1. 序列化配置

  1. 定义 Mysql 和 Redis 的连接信息, 并通过 SetDefaults() 方法设置默认值。 以下这些配置结构体, 可以是自己本地定义, 也可以是 依赖库 中准备好的。 (一般是依赖库提供)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
type MysqlServer struct {
	ListenAddr string `env:"listenAddr"`
	Auth       string `env:"auth"`
	DBName     string `env:"dbName"`
}

func (my *MysqlServer) SetDefaults() {
	if my.ListenAddr == "" {
		my.ListenAddr = "localhost:3306"
	}
}

type RedisServer struct {
	DSN string `env:"dsn"`
}

func (r *RedisServer) SetDefaults() {
	if r.DSN == "" {
		r.DSN = "redis://:Password@localhost:6379/0"
	}
}
  1. 随后再将需要的依赖库中的配置通过 config 结构体组合起来, 并通过 CallSetDefaults 初始化默认值。 最后序列化成字符串。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func Test_ConfP_Server(t *testing.T) {

// 1. 创建配置结构体
	config := &struct {
		MysqlServer *MysqlServer
		RedisServer *RedisServer
	}{
		MysqlServer: &MysqlServer{},
		RedisServer: &RedisServer{},
	}

// 2. 调用设置默认值
	err := CallSetDefaults(config)
	if err != nil {
		panic(err)
	}

// 3. 序列化配置
	data, err := Marshal(config, "AppName")
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", data)
}

2. 反序列化配置

  1. 复制 default.yml 并另存为 config.yml, 修改实际字段的值。
1
2
3
4
AppName__MysqlServer_auth: "DbUser:DbPass"
AppName__MysqlServer_dbName: "default_db"
AppName__MysqlServer_listenAddr: 100.100.100.100:3306
AppName__RedisServer_dsn: redis://:[email protected]:6379/0
  1. 使用 UnmarshalFile 从配置文件中读取配置, 并 映射 到结构体中。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func Test_ConfP_Server(t *testing.T) {

	config := &struct {
		MysqlServer *MysqlServer
		RedisServer *RedisServer
	}{
		MysqlServer: &MysqlServer{},
		RedisServer: &RedisServer{},
	}

	err := UnmarshalFile(config, "AppName", "config.yml")
	if err != nil {
		panic(err)
	}

	fmt.Println("my_auth =>", config.MysqlServer.Auth)
	fmt.Println("redis_dsn =>", config.RedisServer.DSN)
}

输出结果与 config 配置一致。

my_auth => DbUser:DbPass
redis_dsn => redis://:[email protected]:6379/0

补充说明

  1. 这是一个 不完善 的库: 目前只支持 string, int, uint, bool 几种基础数据类型。
  2. 这是一个 基础 库: 这个库只提供了 序列化和反序列化 的能力。 如果要实现 多配置管理 或者 多来源管理 需要在此库上进行二次封装。