Pular para o conteúdo principal

victorstein.dev

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