I’m working on an application that connects to both MySQL and PostgreSQL databases. I’m using Viper to manage the configuration for both databases and I am unsure whether it’s better to unmarshal the configuration into a struct and pass it to the connection functions, or retrieve the values directly using viper.Get(“Key”) inside each function.
Here’s an example of both approaches for context:
Approach 1: Using a struct to load the configuration and pass it to the functions:
// Struct with configuration for MySQL and PostgreSQL
type Config struct {
MysqlHost string `mapstructure:"MYSQL_HOST"`
MysqlPort int `mapstructure:"MYSQL_PORT"`
MysqlUser string `mapstructure:"MYSQL_USER"`
MysqlPwd string `mapstructure:"MYSQL_PASSWORD"`
MysqlName string `mapstructure:"MYSQL_NAME"`
PgHost string `mapstructure:"PG_HOST"`
PgPort int `mapstructure:"PG_PORT"`
PgUser string `mapstructure:"PG_USER"`
PgPwd string `mapstructure:"PG_PASSWORD"`
PgName string `mapstructure:"PG_NAME"`
}
// Load configuration into struct
func loadConfig(path string) (Config, error) {
viper.AddConfigPath(path)
viper.SetConfigName("pat")
viper.SetConfigType("env")
viper.AutomaticEnv()
var config Config
err := viper.ReadInConfig()
if err != nil {
return config, err
}
err = viper.Unmarshal(&config)
return config, err
}
func connectMySQL(config Config) (*sqlx.DB, error) {
mysqlConn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s",
config.MysqlUser, config.MysqlPwd, config.MysqlHost, config.MysqlPort, config.MysqlName)
return sqlx.Connect("mysql", mysqlConn)
}
func connectPostgres(config Config) (*sqlx.DB, error) {
psqlConn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
config.PgHost, config.PgPort, config.PgUser, config.PgPwd, config.PgName)
return sqlx.Connect("postgres", psqlConn)
}
Approach 2: Using viper.Get(Key) directly within the connection functions:
func connectMySQL() (*sqlx.DB, error) {
mysqlConn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s",
viper.GetString("MYSQL_USER"),
viper.GetString("MYSQL_PASSWORD"),
viper.GetString("MYSQL_HOST"),
viper.GetInt("MYSQL_PORT"),
viper.GetString("MYSQL_NAME"))
return sqlx.Connect("mysql", mysqlConn)
}
func connectPostgres() (*sqlx.DB, error) {
psqlConn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
viper.GetString("PG_HOST"),
viper.GetInt("PG_PORT"),
viper.GetString("PG_USER"),
viper.GetString("PG_PASSWORD"),
viper.GetString("PG_NAME"))
return sqlx.Connect("postgres", psqlConn)
}
While I typically prefer the first approach (unmarshalling) for simplicity, is there an advantage to using a global configuration with viper.Get(Key) to avoid sharing the config struct between functions?
Additionally, are there specific advantages, with any of the approaches, in terms of performance, code readability, maintainability, or flexibility when handling large configurations?
Is there a better approach?
I’d advise using some DTO to read a config file, and then put values into the Config object with non-exported properties and exported getters.
This approach helps to remove the possibility of changing a config values after reading. And also helps to read the config and forget about the field names for all further usages.
// DatabaseConfig is a unified connection config for an MySQL/Postgres databases.
type DatabaseConfig struct {
host string
port int
user string
pass string
name string
}
func (c DatabaseConfig) GetHost() string {
return c.host
}
func (c DatabaseConfig) GetPort() int {
return c.port
}
func (c DatabaseConfig) GetUser() string {
return c.user
}
func (c DatabaseConfig) GetPassword() string {
return c.pass
}
func (c DatabaseConfig) GetName() string {
return c.name
}
// Config is a full application config.
type Config struct {
mySQL DatabaseConfig
postgres DatabaseConfig
}
func (c Config) GetMySQLConfig() DatabaseConfig {
return c.mySQL
}
func (c Config) GetPostgresConfig() DatabaseConfig {
return c.postgres
}
type configDTO struct {
MysqlHost string `mapstructure:"MYSQL_HOST"`
MysqlPort int `mapstructure:"MYSQL_PORT"`
MysqlUser string `mapstructure:"MYSQL_USER"`
MysqlPwd string `mapstructure:"MYSQL_PASSWORD"`
MysqlName string `mapstructure:"MYSQL_NAME"`
PgHost string `mapstructure:"PG_HOST"`
PgPort int `mapstructure:"PG_PORT"`
PgUser string `mapstructure:"PG_USER"`
PgPwd string `mapstructure:"PG_PASSWORD"`
PgName string `mapstructure:"PG_NAME"`
}
func loadConfig(path string) (Config, error) {
vpr := viper.New()
vpr.AddConfigPath(path)
vpr.SetConfigName("pat")
vpr.SetConfigType("env")
vpr.AutomaticEnv()
var dto configDTO
if err := vpr.ReadInConfig(); err != nil {
return Config{}, fmt.Errorf("failed to read config file: %w", err)
}
if err := vpr.Unmarshal(&dto); err != nil {
return Config{}, fmt.Errorf("failed to unmarshal config: %w", err)
}
return Config{
mySQL: DatabaseConfig{
host: dto.MysqlHost,
port: dto.MysqlPort,
user: dto.MysqlUser,
pass: dto.MysqlPwd,
name: dto.MysqlName,
},
postgres: DatabaseConfig{
host: dto.PgHost,
port: dto.PgPort,
user: dto.PgUser,
pass: dto.PgPwd,
name: dto.PgName,
},
}, nil
}
// connectMySQL(config.GetMySQLConfig())
func connectMySQL(config DatabaseConfig) (*sqlx.DB, error) {
mysqlConn := fmt.Sprintf(
"%s:%s@tcp(%s:%d)/%s",
config.GetUser(), config.GetPassword(), config.GetHost(), config.GetPort(), config.GetName(),
)
return sqlx.Connect("mysql", mysqlConn)
}
// connectPostgres(config.GetPostgresConfig())
func connectPostgres(config DatabaseConfig) (*sqlx.DB, error) {
psqlConn := fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
config.GetHost(), config.GetPort(), config.GetUser(), config.GetPassword(), config.GetName(),
)
return sqlx.Connect("postgres", psqlConn)