41/100 Dias de Golang - Gerenciando Configurações
Table of Contents
#
Gerenciando Configurações
Gerenciamento de configurações e variáveis é uma etapa bem importante para qualquer projeto em qualquer linguagem. Como venho do Python, estava buscando algo parecido com o Dynaconf. Vou mostrar o gerenciamento de variáveis de ambiente sem nenhuma lib em Golang e depois usando a bibilioteca envconfig
e Viper
. Separar configuração do códig é uma prática fundamental. Isso facilita o deploy em diferentes ambientes sem precisar alterar o código. É também uma recomendação clássica do manifesto Twelve-Factor App. Veja esse exemplo bem simples utilizando o os.Getenv
import (
"fmt"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
fmt.Println("Servidor rodando na porta:", port)
}
Para executar, vamos definir uma variável PORT=9090.
PORT=9090 go run main.go
Servidor rodando na porta: 9090
Porém essa forma não escala muito bem, podemos ter diversas variáveis que serão utilizadas em várias funções e rotinas. Para organizar podemos utilizar structs
e criar um loader de configs, essa forma já possui mais organização de código.
type Config struct {
Port string
DBUrl string
LogLevel string
}
func LoadConfig() Config {
return Config{
Port: getEnv("PORT", "8080"),
DBUrl: getEnv("DATABASE_URL", ""),
LogLevel: getEnv("LOG_LEVEL", "info"),
}
}
func getEnv(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
#
Utilizando envconfig
Outra opção é o uso da bilbioteca envconfig que abstrai esse código e já lida com conversões de tipo, valores padrão e validações de campos obrigatórios. Veja que essa lib usa o padrão de tag nas structs para definir nome da variável de ambiente, valor padrão e se o campo é obrigatório. Veja o exemplo:
package main
import (
"fmt"
"github.com/kelseyhightower/envconfig"
)
type Config struct {
Port string `envconfig:"PORT" default:"8080"`
DBUrl string `envconfig:"DATABASE_URL" required:"true"`
LogLevel string `envconfig:"LOG_LEVEL" default:"info"`
}
func LoadConfig() (Config, error) {
var cfg Config
err := envconfig.Process("", &cfg)
return cfg, err
}
func main() {
c, err := LoadConfig()
if err != nil {
panic(err)
}
fmt.Printf("PORT: %s DATABASE_URL: %s LOG_LEVEL: %s", c.Port, c.DBUrl, c.LogLevel)
}
Perceba que o DBUrl string `envconfig:"DATABASE_URL" required:"true"
está com a tag required:true
, caso não seja passada essa variável o LoadConfig
retornará um err
e o código irá entrar no panic()
#
Utilizando Viper
Viper é uma biblioteca para gerenciamento de configurações. Ela permite carregar configurações de arquivos (.json, .yaml, .toml, etc.), variáveis de ambiente, flags e muito mais. Para instalar no nosso projeto:
go get github.com/spf13/viper
Vamos criar um arquivo config.yaml
para armazenar nossas variáveis
app:
name: "Blog"
port: 8081
database:
host: "localhost"
port: 5432
user: "victostein"
password: "424242"
Veja como fica o script:
package main
import (
"fmt"
"log"
"github.com/spf13/viper"
)
func initConfig() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
log.Fatalf("Erro ao ler o arquivo de configuração: %v", err)
}
}
func main() {
initConfig()
appName := viper.GetString("app.name")
appPort := viper.GetInt("app.port")
dbHost := viper.GetString("database.host")
fmt.Printf("Aplicação: %s rodando na porta %d\n", appName, appPort)
fmt.Printf("Banco de dados rodando em: %s\n", dbHost)
}
Toda a “mágica” acontece na função initConfig
. Com a função viper.SetConfigName
definimos o nome do arquivo, mas somente o nome, a extensão será passada na função viper.SetConfigType
e o path do arquivo será passado no AddConfigPath
e o ReadInConfig
faz a leitura do arquivo. E para leitura das variáveis usamos os métodos GetString
e GetInt
, ou outro Get.
#
Variáveis de ambiente com o Viper
A biblioteca possui uma função muito legal: viper.AutomaticEnv()
que já procura automaticamente variáveis de ambiente. Adicione esse código no exemplo anterior.
viper.AutomaticEnv()
host := viper.GetString("host")
fmt.Printf("Aplicação: %s rodando na porta %d host: %s\n", appName, appPort, host)
fmt.Printf("Banco de dados rodando em: %s\n", dbHost)
HOST=victorstein.dev go run main.go
Aplicação: Blog rodando na porta 8081 host: victorstein.dev
Banco de dados rodando em: localhost