注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用
本系列为 Go 进阶训练营 笔记,访问 博客: Go进阶训练营, 即可查看当前更新进度,部分文章篇幅较长,使用 PC 大屏浏览体验更佳。
3 月进度: 03/15 3 月开始会尝试爆更模式,争取做到两天更新一篇文章,如果感兴趣可以拉到文章最下方获取关注方式。
应用的配置大概可以分为以下几种
下面这个是 redis 初始化的例子,一般在我们刚刚开始写代码的时候,我们都会向下面这么写,把需要的参数放到函数的入参就行了。
1 | |
这个有什么问题呢?如果这个函数只是你自己用也没有什么毛病,但是如果是一个公共的库或者是中间件就会发现,用户的诉求是多种多样,灵活多变的。就会听见
等等一系列的需求和各种各样的声音。
这时候为了满足大家的需求,最简单,最直接的做法就是,为不同的需求添加不同的初始化函数
1 | |
但这样毕竟不是一个办法,因为用户的需求是满足不完的,作为公共库,不可能为每个用户的需求都单独来搞个函数签名,那这样函数签名也太多了。而且还有一个问题是参数列表会很长,例如上面的 DialTimeout , 可读性也不好。
当然这也和 Go 的函数不能重载有关系,如果可以重载的话,每种需求来一个可能也还行,但是其实也不够优雅。
这时候我们比较容易想到的办法是什么呢?既然参数比较长,配置变化又想要灵活,那么我们就直接传入一个对象就好了,让每个用户自己构造去。
1 | |
这种方式有什么问题呢?
NewConn 传递的是一个指针,那么这个只能就能够被外面修改,只要外面修改那就麻烦了,因为不知道会发生什么,这是一个未定义的行为。既然指针可能会导致未定义的行为,那我们就换个方式, 不传指针传结构体不就行了
1 | |
但是这又带来了一些新的问题
所以,有一段时间毛老师他们都是使用上面传指针的这种方式,当然这种方式我们也用过,虽然可以用,但是就是有点不爽
“I believe that we, as Go programmers, should work hard to ensure that nil is never a parameter that needs to be passed to any public function.” – Dave Cheney
dava 大神也提到过,我们应该将 nil 作为一个函数的参数值进行传递,那我们该如何修改呢?
如果去看一些知名的开源库或者是标准库的一些初始化代码,我们可以看到这种姿势
1 | |
这种操作的核心在于,我们可以定义一个未导出的 option struct 用于存放配置,然后到处一个函数指针,然后我们在初始化的时候,使用可变参数进行传递,然后再初始化函数内部通过 for 循环调用修改相关的配置。
我们可以在包里面直接定义一些函数例如上面的 DialReadTimeout 来返回一个函数,然后进行配置修改
但是这样就可以了么?这种使用方式
type DialOption func(*dialOptions)如果想要用户可以自定义一些配置,我们可以看看 grpc 的配置定义,主要的思路就是把 option 从函数修改接口,然后定义了一个 EmptyCallOption 实现这个接口,因为这个接口包含的函数是未导出的,所以我们只要在需要做配置的 struct 当中包含这个 EmptyCallOption 就可以了
1 | |
到这里函数的配置就解决了,但是我们怎么和配置文件进行结合呢?现在的这种做法隐藏了结构,没有办法直接使用 json.Unmarshal 这种方法直接反序列化回来。
比较常见的办法就是我们设定两个函数如果需要配置文件反序列化的就用不带 Option 的,反之用带 Option 的
1 | |
这么做比较大的问题就是,把 config 给暴露了出来,并且有两种初始化方式,使用配置文件就没有办法得到使用 Option 的好处了
课上提供了一种解决思路就是把这两步进行分离,首先我们使用 protobuf 文件定义好配置的结构,这样可以加上一些验证条件
1 | |
定义好之后使用 yaml 来修改配置,然后使用 Options 方法,将 protobuf 生成的 Config 替换为 redis.Options
1 | |
这种方式除了定义起来比较麻烦,使用上还是很简单的,使用只需要像下面这样就可以了
1 | |
由于我们现在使用的没有那么复杂,统一接入了配置中心,所以我现在的做法是定义一个 WithConfigCenter 的方法就行了,调用的时候其实还要简单一点
1 | |
修改配置其实是一件比较危险的事情,很多时候我们缺乏足够的敬畏,因为现在在线的配置中心越来方便,所以修改的成本越来越低,大家就越来越随意,所以我们需要对配置的修改慎重一些。配置的目标:
注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用
本系列为 Go 进阶训练营 笔记,访问 博客: Go进阶训练营, 即可查看当前更新进度,部分文章篇幅较长,使用 PC 大屏浏览体验更佳。
3 月进度: 03/15 3 月开始会尝试爆更模式,争取做到两天更新一篇文章,如果感兴趣可以拉到文章最下方获取关注方式。
应用的配置大概可以分为以下几种
下面这个是 redis 初始化的例子,一般在我们刚刚开始写代码的时候,我们都会向下面这么写,把需要的参数放到函数的入参就行了。
1 | |
这个有什么问题呢?如果这个函数只是你自己用也没有什么毛病,但是如果是一个公共的库或者是中间件就会发现,用户的诉求是多种多样,灵活多变的。就会听见
等等一系列的需求和各种各样的声音。
这时候为了满足大家的需求,最简单,最直接的做法就是,为不同的需求添加不同的初始化函数
1 | |
但这样毕竟不是一个办法,因为用户的需求是满足不完的,作为公共库,不可能为每个用户的需求都单独来搞个函数签名,那这样函数签名也太多了。而且还有一个问题是参数列表会很长,例如上面的 DialTimeout , 可读性也不好。
当然这也和 Go 的函数不能重载有关系,如果可以重载的话,每种需求来一个可能也还行,但是其实也不够优雅。
这时候我们比较容易想到的办法是什么呢?既然参数比较长,配置变化又想要灵活,那么我们就直接传入一个对象就好了,让每个用户自己构造去。
1 | |
这种方式有什么问题呢?
NewConn 传递的是一个指针,那么这个只能就能够被外面修改,只要外面修改那就麻烦了,因为不知道会发生什么,这是一个未定义的行为。既然指针可能会导致未定义的行为,那我们就换个方式, 不传指针传结构体不就行了
1 | |
但是这又带来了一些新的问题
所以,有一段时间毛老师他们都是使用上面传指针的这种方式,当然这种方式我们也用过,虽然可以用,但是就是有点不爽
“I believe that we, as Go programmers, should work hard to ensure that nil is never a parameter that needs to be passed to any public function.” – Dave Cheney
dava 大神也提到过,我们应该将 nil 作为一个函数的参数值进行传递,那我们该如何修改呢?
如果去看一些知名的开源库或者是标准库的一些初始化代码,我们可以看到这种姿势
1 | |
这种操作的核心在于,我们可以定义一个未导出的 option struct 用于存放配置,然后到处一个函数指针,然后我们在初始化的时候,使用可变参数进行传递,然后再初始化函数内部通过 for 循环调用修改相关的配置。
我们可以在包里面直接定义一些函数例如上面的 DialReadTimeout 来返回一个函数,然后进行配置修改
但是这样就可以了么?这种使用方式
type DialOption func(*dialOptions)如果想要用户可以自定义一些配置,我们可以看看 grpc 的配置定义,主要的思路就是把 option 从函数修改接口,然后定义了一个 EmptyCallOption 实现这个接口,因为这个接口包含的函数是未导出的,所以我们只要在需要做配置的 struct 当中包含这个 EmptyCallOption 就可以了
1 | |
到这里函数的配置就解决了,但是我们怎么和配置文件进行结合呢?现在的这种做法隐藏了结构,没有办法直接使用 json.Unmarshal 这种方法直接反序列化回来。
比较常见的办法就是我们设定两个函数如果需要配置文件反序列化的就用不带 Option 的,反之用带 Option 的
1 | |
这么做比较大的问题就是,把 config 给暴露了出来,并且有两种初始化方式,使用配置文件就没有办法得到使用 Option 的好处了
课上提供了一种解决思路就是把这两步进行分离,首先我们使用 protobuf 文件定义好配置的结构,这样可以加上一些验证条件
1 | |
定义好之后使用 yaml 来修改配置,然后使用 Options 方法,将 protobuf 生成的 Config 替换为 redis.Options
1 | |
这种方式除了定义起来比较麻烦,使用上还是很简单的,使用只需要像下面这样就可以了
1 | |
由于我们现在使用的没有那么复杂,统一接入了配置中心,所以我现在的做法是定义一个 WithConfigCenter 的方法就行了,调用的时候其实还要简单一点
1 | |
修改配置其实是一件比较危险的事情,很多时候我们缺乏足够的敬畏,因为现在在线的配置中心越来方便,所以修改的成本越来越低,大家就越来越随意,所以我们需要对配置的修改慎重一些。配置的目标: