catalogue
- Configuration classification
- Best practices
- configuration management
- Reference link
1. Configuration classification
Environment configuration
The information that the application should determine during deployment should not be written in the configuration file or configuration center, but should be injected by the deployment platform when the application is started, for example k8s directly when the container is started
Static configuration
The information required during application resource initialization, such as basic information of services, Mysql, Redis, Mongodb and other configuration information, is generally a static file placed in the root directory of the project
Dynamic configuration
Some configurations are required when the application is running, similar to some function switches, which are generally controlled in the management background
2. Best practices
2.1 mode I
json static file management configuration file configuration
{ "server": { "addr": "0.0.0.0:8000" }, "mysql": { "driver": "mysql", "dsn": "root:@tcp(127.0.0.1:3306)/testdb?parseTime=True" } }
The project depends on the global 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) } } // Connect to mysql func NewMysql(driver, dsn string) (*sql.DB, error) { return sql.Open(driver, dsn) }
2.2 mode II
Create a Config structure type, the business depends on this structure, and resolve the json configuration to this structure
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. Configuration management
Redis Server configuration
// Server defines options for redis cluster. type Server struct { Addr string Password string Database int DialTimeout time.Duration }
Required item cannot be blank
- Address Addr
- Password password
Optional items can be blank and have default values
- Database database
- Timeout
For the above configuration, there are multiple ways to construct
3.1 method 1: multiple function signatures
Create a generic constructor
func NewServer(addr string, password string) (*Server, error) { return &Server{addr, password, 0, time.Second}, nil }
Customize the database and timeout. Go does not support function overloading. Use different function signatures to implement the corresponding configuration
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 }
Disadvantages:
- If we want to customize more requirements, don't we add a function signature for each requirement
- Function parameters will be very long, which is not conducive to expansion
- Not an elegant gesture
3.2 mode 2: transfer configuration structure
Pass a configured structure and put all the optional items into it to solve the long parameters and various user-defined requirements
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 }
call
NewServer("127.0.0.1:6379", "password", &Option{Database: 1, DialTimeout: time.Second})
Disadvantages:
- For internal use, you need to first judge whether the pointer is nil
- Because a pointer is passed, it can be modified outside the function. This is not the expected behavior, and unpredictable things will happen
- There is no way to distinguish between default values
3.3 mode 3: Builder mode
// ServerBuilder uses a builder class as a wrapper 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 }
call
builder := ServerBuilder{} builder.WithAddr("127.0.0.1:6379").WithPassword("pwd").WithTimeout(2 * time.Second).WithDatabase(8).Build()
Disadvantages:
- You need to add a new structure to package
- If you do not add a new structure and build it directly on the Server, you need to add an error member when handling errors, which is impure
3.4 mode 4: Function Optional
Define a function type
type Opt func(option *Option)
Define correlation function
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 }
call
NewServer("127.0.0.1:6379", "password", WithTimeout(2*time.Second), WithDatabase(8))
Advantages:
- Order independent
- Better maintainability and scalability
- More intuitive and lower cost
- Nothing confusing (nil or empty)