Go配置管理探讨

目录

  • 配置分类
  • 最佳实践
  • 配置管理
  • 参考链接

1. 配置分类

环境配置

应用在部署的时候应该确定的信息,这些信息不应该写在配置文件或者配置中心,而是应该由部署平台在应用启动的时候注入,例如k8s直接在容器启动的时候注入

静态配置

应用资源初始化的时候需要的信息,如服务的基本信息、Mysql、Redis、Mongodb等配置信息,一般是放在项目的根目录下的静态文件

动态配置

应用程序在运行是的时候需要的一些配置,类似某些功能开关,一般是在管理后台去控制

2. 最佳实践

2.1 方式一

json静态文件管理配置文件配置

{
  "server": {
    "addr": "0.0.0.0:8000"
  },
  "mysql": {
    "driver": "mysql",
    "dsn": "root:@tcp(127.0.0.1:3306)/testdb?parseTime=True"
  }
}

项目依赖全局的Config map

package main

import (
   "database/sql"
   "encoding/json"
   "net/http"
   "os"
)

var Config = make(map[string]map[string]string)

func main() {

   file, err := os.Open("/path/to/config/config.json")
   if err != nil {
      panic(err)
   }
   decoder := json.NewDecoder(file)
   err = decoder.Decode(&Config)
   if err != nil {
      panic(err)
   }
     defer file.Close()
  
   db, err := NewMysql(Config["mysql"]["driver"], Config["mysql"]["driver"])
   if err != nil {
      panic(err)
   }
   server := &http.Server{
      Addr: Config["server"]["addr"],
   }
   err = server.ListenAndServe()
   if err != nil {
      panic(err)
   }
}

// 连接mysql
func NewMysql(driver, dsn string) (*sql.DB, error) {
   return sql.Open(driver, dsn)
}

2.2 方式二

创建一个Config结构体类型,业务依赖这个结构体,并且将json配置解析到这个结构体

import (
   "database/sql"
   "encoding/json"
   "net/http"
   "os"
)

var Config C

func main() {

   file, err := os.Open("/path/to/config/config.json")
   if err != nil {
      panic(err)
   }
   decoder := json.NewDecoder(file)
   err = decoder.Decode(&Config)
   if err != nil {
      panic(err)
   }
   defer file.Close()
   
   db, err := NewMysql(Config.Mysql)

   if err != nil {
      panic(err)
   }
   server := &http.Server{
      Addr: Config.Server.Addr,
   }
   err = server.ListenAndServe()
   if err != nil {
      panic(err)
   }
}

type C struct {
   Server *Server
   Mysql  *Mysql
}

type Server struct {
   Addr string `json:"addr"`
}

type Mysql struct {
   Driver string `json:"driver"`
   Dsn    string `json:"dsn"`
}

func NewMysql(mysql *Mysql) (*sql.DB, error) {
   return sql.Open(mysql.Driver, mysql.Dsn)
}

3. 配置管理

Redis Server 配置

// Server defines options for redis cluster.
type Server struct {
    Addr         string
    Password     string
    Database     int
    DialTimeout  time.Duration     
}
  • 必填项 不能为空

    • 地址Addr
    • 密码 Password
  • 选填项 可以为空,有默认值

    • 数据库Database
    • 超时时间Timeout

针对上面的配置,有多类方式去构造

3.1 方式一:多种函数签名

创建一个通用的构造器

func NewServer(addr string, password string) (*Server, error) {
    return &Server{addr, password, 0, time.Second}, nil
}

自定义databasetimeout,Go不支持函数重载,用不同的函数签名去实现对应的配置

func NewServerWithDatabase(addr string, password string,database int)(*Server, error){
    return &Server{addr, password, database, time.Second}, nil
}

func NewServerWithTimeout(addr string, password string,timeout time.Duration)(*Server, error){
    return &Server{addr, password, 0, timeout}, nil
}

弊端 :

  • 如果我们要自定义更多的需求之后,岂不是要针对每一种需求都要新增一个函数签名
  • 函数参数会非常长,不利于扩展
  • 不是优雅的姿势

3.2 方式二:传递配置结构体

传递一个配置的结构体,把选填项都放到这个结构体中,解决参数比较长和各种自定义需求

type Server struct {
    Addr     string
    Password string
    Option   *Option
}

type Option struct {
    Database    int
    DialTimeout time.Duration
}
func NewServer(addr string, password string, option *Option) (*Server, error) {
    return &Server{addr, password, option}, nil
}


func NewServer(addr string, password string,option *Option) (*Server, error) {
    return &Server{addr, password, option}, nil
}

调用

NewServer("127.0.0.1:6379", "password", &Option{Database: 1, DialTimeout: time.Second})

弊端 :

  • 内部使用的时候,需要先判断指针是否为nil
  • 因为传递的是一个指针,可以在函数外边进行修改,这不是预期内的行为,会发生不可预知的事情
  • 没办法区分默认值

3.3 方式三:Builder模式

// ServerBuilder 使用一个builder类来做包装
type ServerBuilder struct {
   Server
   Err error
}

func (s *ServerBuilder) WithAddr(addr string) *ServerBuilder {
   s.Server.Addr = addr
   return s
}

func (s *ServerBuilder) WithPassword(pwd string) *ServerBuilder {
   s.Server.Password = pwd
   return s
}

func (s *ServerBuilder) WithTimeout(tw time.Duration) *ServerBuilder {
   s.Server.Option.DialTimeout = tw
   return s
}

func (s *ServerBuilder) WithDatabase(db int) *ServerBuilder {
   s.Server.Option.Database = db
   return s
}

func (s *ServerBuilder) Build() Server {
   return s.Server
}

调用

builder := ServerBuilder{}
builder.WithAddr("127.0.0.1:6379").WithPassword("pwd").WithTimeout(2 * time.Second).WithDatabase(8).Build()

弊端 :

  • 需要新增结构体来包装
  • 如果不新增结构体在Server上直接build的话,处理错误的时候要新增error成员,不纯洁

3.4 方式四:Function Optional

定义一个函数类型

type Opt func(option *Option)

定义相关函数

func WithDatabase(database int) Opt {
    return func(option *Option) {
        option.Database = database
    }
}

func WithTimeout(to time.Duration) Opt {
    return func(option *Option) {
        option.DialTimeout = to
    }
}

func NewServer(addr string, password string, opts ...Opt) (*Server, error) {

    option := &Option{
        Database:    0,
        DialTimeout: time.Second,
    }
    for _, opt := range opts {
        opt(option)
    }
    return &Server{
        Addr: addr,
        Password: password,
        Option:option,
    },nil
}

调用

NewServer("127.0.0.1:6379", "password", WithTimeout(2*time.Second), WithDatabase(8))

优势:

  • 顺序无关
  • 可维护性和扩展性更好
  • 更直观,使用成本更低
  • 没有什么令人困惑的事(是nil 还是空)

4. 参考链接

你可能感兴趣的:(golangconfig)