DEV Community

Wako
Wako

Posted on

Leer un archivo de configuración para tu aplicación de Go

¿Qué es un archivo de configuración?

Un archivo de configuración es un archivo de texto plano que puede ser modificado por el usuario para dar ciertos parámetros de configuración a una aplicación. Un ejemplo de estos serían los archivos de configuración de Apache o el archivo de configuración de Git.

¿Existe algún formato específico?

No, no existe ningún formato específico. Bien podría ser un xml, un yml, un json o incluso existen programas que crean su propia sintaxis para sus archivos de configuración. Para este ejemplo usaremos un archivo json.

Nuestro proyecto

Tendremos un proyecto de ejemplo con el siguiente árbol de directorios:

.
├── Makefile
├── cmd
│   └── app
│       └── main.go
├── config
│   └── config.go
├── config.json
├── go.mod
└── service
    └── foo.go
Enter fullscreen mode Exit fullscreen mode

Definiendo nuestro paquete de configuración

Este paquete se encargará de leer nuestro archivo de configuración. Además tendrá que hacerlo una sola vez, ya que la configuración con cambiará durante la ejecución de nuestra aplicación.

Para esto tenemos que aplicar un patrón de diseño conocido como singleton. Lo cual crea una única instancia de un tipo de dato en nuestra aplicación.

Dentro de config/config.go creamos una estructura para almacenar la información de nuestra configuración y un puntero de la misma.

type Config struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

var config *Config
Enter fullscreen mode Exit fullscreen mode

Exponemos una función llamada GetConfig la cual retorna un *Config. Dentro de esta función revisamos que el puntero config declarado anteriormente se encuentre vacío.

if config == nil {
  log.Println("Loading config file...")
}
Enter fullscreen mode Exit fullscreen mode

Una vez asegurados que config no contiene información podemos abrir el archivo de configuración utilizando el path del archivo. En este ejemplo el path se pasa directamente, pero bien podría tomarse de una variable de entorno para definir otra ruta de archivo.

file, _ := os.Open("config.json")
defer file.Close()
Enter fullscreen mode Exit fullscreen mode

Una vez abierto el archivo se crea un decoder de tipo JSON para poder leerlo. Y en caso de error se colocan valores por defecto en una variable de tipo Conf.

decoder := json.NewDecoder(file)
var conf Config
err := decoder.Decode(&conf)
if err != nil {
    conf.Name = "Default Name"
    conf.Age = 21
}
Enter fullscreen mode Exit fullscreen mode

Para poder pasar los valores de la variable conf al puntero config creamos un Sync.Once.

var config *Config
var once sync.Once
Enter fullscreen mode Exit fullscreen mode

Y dentro de nuestra función de GetConfig utilizamos once para poder pasar nuestra info a config.

once.Do(func() {
  config = &Config{
    Name: conf.Name,
    Age:  conf.Age,
  }
})
Enter fullscreen mode Exit fullscreen mode

Y por último GetConfig retornará config después de nuestra validación.

func GetConfig() *Config {
    if config == nil {
        log.Println("Loading config file...")
        file, _ := os.Open("config.json")
        defer file.Close()

        decoder := json.NewDecoder(file)
        var conf Config
        err := decoder.Decode(&conf)
        if err != nil {
            conf.Name = "Default Name"
            conf.Age = 21
        }

        once.Do(func() {
            config = &Config{
                Name: conf.Name,
                Age:  conf.Age,
            }
        })
    }

    return config
}
Enter fullscreen mode Exit fullscreen mode

Utilizando nuestra configuración

Nuestro cmd se encargará únicamente de iniciar nuestra aplicación llamando la función Start dentro de service/foo.go.

package main

import "config/service"

func main() {
    service.Start()
}
Enter fullscreen mode Exit fullscreen mode

Y service/foo.go tendrá la funcionalidad de nuestra aplicación. Donde llamaremos a GetConfig para obtener nuestra configuración.

Dentro de la función Start llamaremos por múltiples veces a GetConfig y notaremos que la inicialización del puntero sólo sucede una vez.

Obtenemos la configuración y la utilizamos para imprimir un mensaje en pantalla. Además ejecutamos las funciones foo y bar.

func Start() {
    conf := config.GetConfig()
    fmt.Printf("Hello %s. You are %d years old.\n", conf.Name, conf.Age)
    foo()
    bar()
}
Enter fullscreen mode Exit fullscreen mode

Dentro de foo y bar haremos algo similar. Obtendremos nuestra configuración y la utilizaremos para imprimir un mensaje en pantalla.

func foo() {
    conf := config.GetConfig()
    fmt.Printf("Hello from foo> %s | %d\n", conf.Name, conf.Age)
}

func bar() {
    conf := config.GetConfig()
    fmt.Printf("Hello from bar> %d | %s\n", conf.Age, conf.Name)
}
Enter fullscreen mode Exit fullscreen mode

Compilamos el proyecto y podemos ver que el log colocado dentro de GetConfig sólo se muestra una vez. Además podemos notar que se están utilizando los valores por defecto colocados en el manejo del error en GetConfig. Esto porque no existe aún el archivo config.json dentro de nuestro proyecto.

$ make compile
$ ./app                
2022/06/18 16:50:40 Loading config file...
Hello Default Name. You are 21 years old.
Hello from foo> Default Name | 21
Hello from bar> 21 | Default Name
$ 
Enter fullscreen mode Exit fullscreen mode

Creamos el archivo config.json con el siguiente contenido:

{
  "age": 29,
  "name": "Wako"
}
Enter fullscreen mode Exit fullscreen mode

Y ejecutamos el programa de nuevo. No es necesario compilar.

$ ./app      
2022/06/18 16:53:18 Loading config file...
Hello Wako. You are 29 years old.
Hello from foo> Wako | 29
Hello from bar> 29 | Wako
$
Enter fullscreen mode Exit fullscreen mode

De esta forma es fácil proveer de nuestros programas de Go archivos de configuración. Pueden encontrar el código completo aquí. Además acá pueden ver un ejemplo más complejo aplicado a un generador de arte digital.

Top comments (1)

Collapse
 
dennistobar profile image
Dennis Tobar

¡Hola!, buen post de Go :) Par ami a veces es medio caha negra este lenguaje, aunque entiendo la sintaxis y muchas cosas, pero a la hora de las corutinas, bajo la cortina y cierro para dedicarme a otras cosas (solo pereza, no por conocimiento).
Te recomiento usar el tag #spanish para que la comunidad que hablamos español podamos encontrar tu post más fácilmente.

Saludos desde Chile 🇨🇱