15|配置和环境(上):配置服务中的设计思路
创始人
2025-05-28 11:22:13

业务中有大量的配置项,比如数据库的用户名密码、缓存 Redis 的 IP 和端口、第三方调用的地址等。如何通过统一的方法快速获取到这些配置项,就是今天要讨论的内容。

获取配置项有读取本地配置文件、读取远端配置服务、获取环境变量方法。

环境变量获取配置思路分析

为一个程序设置环境变量的方式是多种多样的。如果是容器化的进程,可以在创建镜像的时候设置,也可以在容器启动的时候设置,在 Linux 系统中,我们也可以通过在启动进程的时候,通过前面加上“KEY=VALUE”的方式,为单个进程设置环境变量,比如:
FOO_ENV=bar ./hade foo

不管是哪种设置环境变量的方式,在 Golang 中,都能通过 os 标准库的os.Environ()获取。Environ 方法,会将进程中设置的所有环境变量,都以字符串数组形式返回,每个字符串为“KEY=VALUE”的环境变量设置。

默认环境变量使用一个以 dot 点号开头的文件.env 来进行设置。

其实使用.env 文件来设置默认环境变量,在运行的时候再使用真实的环境变量替换部分默认值,这种做法,在业界已经是一种非常普遍的加载环境变量的方式了。比如,在 Docker 中有个 env-file 配置项,会在启动的时候读取默认环境变量文件;又比如 Vue 中,Webpack 打包各种不同环境的时候,会根据根目录下.env 文件读取环境配置。

hade框架也是如此,在baseFolder下存放一个.env文件,每行为key=value形式的环境变量。

有了上面对环境变量的设置、读取环境变量获取配置的分析之后,我们就可以开始着手设计了。分成两步来写:

  • 环境变量服务的接口及具体实现
  • 读取配置文件服务的接口及具体实现

环境变量服务的接口和实现

环境变量也作为一个接口服务,它有四个方法:


package contractconst (// EnvProduction 代表生产环境EnvProduction = "production"// EnvTesting 代表测试环境EnvTesting = "testing"// EnvDevelopment 代表开发环境EnvDevelopment = "development"// EnvKey 是环境变量服务字符串凭证EnvKey = "hade:env"
)
// Env 定义环境变量服务
type Env interface {// AppEnv 获取当前的环境,建议分为 development/testing/productionAppEnv() string// IsExist 判断一个环境变量是否有被设置IsExist(string) bool// Get 获取某个环境变量,如果没有设置,返回""Get(string) string// All 获取所有的环境变量,.env 和运行环境变量融合后结果All() map[string]string
}

其中AppEnv表示当前运行模式,可以为development/testing/production

下面就来说接口的具体实现,在框架文件 framework/provider/env/service.go 中,大致就是按照先读取本地默认.env 文件,再读取运行环境变量。

创建HadeEnv,其folder代表存放默认环境变量的.env文件目录,maps表示读取默认和程序运行中所有的环境变量:

// HadeEnv 是 Env 的具体实现
type HadeEnv struct {folder string            // 代表.env所在的目录maps   map[string]string // 保存所有的环境变量
}// NewHadeEnv 有一个参数,.env文件所在的目录
// example: NewHadeEnv("/envfolder/") 会读取文件: /envfolder/.env
// .env的文件格式 FOO_ENV=BAR
func NewHadeEnv(params ...interface{}) (interface{}, error) {if len(params) != 1 {return nil, errors.New("NewHadeEnv param error")}// 读取folder文件folder := params[0].(string)// 实例化hadeEnv := &HadeEnv{folder: folder,// 实例化环境变量,APP_ENV默认设置为开发环境maps: map[string]string{"APP_ENV": contract.EnvDevelopment},}// 解析folder/.env文件file := path.Join(folder, ".env")// 读取.env文件, 不管任意失败,都不影响后续// 打开文件.envfi, err := os.Open(file)if err == nil {defer fi.Close()// 读取文件br := bufio.NewReader(fi)for {// 按照行进行读取line, _, c := br.ReadLine()if c == io.EOF {break}// 按照等号解析s := bytes.SplitN(line, []byte{'='}, 2)// 如果不符合规范,则过滤if len(s) < 2 {continue}// 保存mapkey := string(s[0])val := string(s[1])hadeEnv.maps[key] = val}}// 获取当前程序的环境变量,并且覆盖.env文件下的变量for _, e := range os.Environ() {pair := strings.SplitN(e, "=", 2)if len(pair) < 2 {continue}hadeEnv.maps[pair[0]] = pair[1]}// 返回实例return hadeEnv, nil
}

ps: 环境变量也是一个服务,服务实例生成时先读.env文件,再读运行时动态配置的环境变量,最终写入maps字段。其.env文件的存储路径则从app服务获取,因此要先Bind上App的provider,不然会报错。

读取配置服务的接口

一般配置文件存放多个,按功能划分,如databse.yaml、 deploy.yaml等。

配置文件存放在app的ConfigFolder目录下,再根据运行环境(development、production、testing)划分。

根据点号分隔路径进行读取,如app.data.name, 则可能是app路径下data文件的name字段。读取时先判断运行环境,即Env服务的AppEnv属性。

对配置服务封装,Get()、 Exist()、Load() 分别代表获取某个配置项、判断配置项是否存在、将配置项加载到某个对象。

type Config interface {IsExist(key string) boolGet(key string) interface{}Load(key string, val interface{}) error
}

【疑问】:
后续读取配置项,要将前面读取的环境变量,也要作为配置项吗?

相关内容

热门资讯

3小时高铁、畅享温泉滑雪,六安... 最近,不少上海人的朋友圈里,晒的不再是北海道粉雪或长白山雾凇,而是海拔超千米的大别山雪场和热气氤氲的...
脾胃不好,少吃这3类水果 水果是公认的健康食物,不仅能满足口腹之欲,还可以增加维生素、矿物质、膳食纤维等营养物质的摄入。 但是...
河南人过年送整箱火腿肠,背后的... 每逢新春佳节,河南很多地区的人们走亲访友,总少不了送上整箱火腿肠。这一看似寻常的节礼,背后却承载着一...
甘肃平凉崆峒区:地道风物引客来 近日,甘肃省平凉市崆峒区绿地广场上,一串串红灯笼与古韵妆饰的摊位相映成趣,空气中弥漫着美食的香气与喜...
无锡市周边亲子游景点推荐 在忙碌的生活中,亲子游成为了增进亲子关系、开阔孩子视野的绝佳方式。无锡这座美丽的城市周边,有着众多适...