Los que ya lleváis un tiempo considerable en el mundo del desarrollo web es probable que estéis, directa o indirectamente, familiarizados con “the twelve-factor app”, el cuál se trata de un manifiesto que se define como una metodología para construir aplicaciones SaaS. En este manifiesto se definen esos (doce) “twelve factors” que podríamos interpretar como consejos a tener en cuenta a la hora de desarrollar nuestras aplicaciones. El tercero de ellos es relativo a la “configuración” y básicamente nos dice que deberíamos “guardar la configuración en el entorno”.
Como bien indican los autores de dicho manifiesto, “la configuración de una aplicación es todo lo que puede variar entre despliegues”. Y pese a que “a veces las aplicaciones guardan configuraciones como constantes en el código”, eso lo consideran como una violación de la metodología “twelve-factor”, cuya filosofía se basa en “una estricta separación de la configuración y el código”. Pues, como sabemos “la configuración varía sustancialmente en cada despliegue, el código no”.
Además, en el propio manifiesto también nos sugieren la forma de llevarlo a cabo, mediante “el almacenamiento de la configuración en variables de entorno (normalmente abreviadas como “env vars”). La idea aquí es que “las variables de entorno se puedan modificar fácilmente entre despliegues sin realizar cambios en el código” y además “son un estándar independiente del lenguaje y del sistema operativo”.
Así que, ahora que ya sabemos que es recomendable usar las variables de entorno para leer la configuración de nuestras aplicaciones, veamos como podemos hacerlo en Go.
Variables de entorno y el paquete “os”
La primera y la más sencilla manera de leer las variables de entorno es con el paquete "os"
,
de la librería estándar, pues este nos proporciona funciones para
comprobar si una variable de entorno está definida
(os.LookupEnv(key string) (string, bool)
) o para simplemente obtener el valor de dicha variable
(os.Getenv(key string) string
).
Esto, además, nos permite hacernos algunas pequeñas funciones auxiliares del estilo de:
func Getenv(key, defaultValue string) string {
value, defined := os.LookupEnv(key)
if !defined {
return defaultValue
}
return value
}
Cuya finalidad no es otra que la de proporcionarnos una sencilla manera de obtener el valor de una variable de entorno,
en caso que esté definida, o un valor por defecto en caso contrario. Además, la funcionalidad que nos proporciona
el paquete "os"
en relación con las variables de entorno no queda aquí, sino que además también nos permite
modificar el entorno:
- La función
os.Setenv(key, value string) error
nos permite configurar una nueva variable de entorno. - La función
os.Unsetenv(key string) error
nos permite eliminar una variable de entorno. - La función
os.Clearenv()
nos permite eliminar todas las variables de entorno.
Adicionalmente, dentro del paquete "os"
también tenemos un par de utilidades más para leer y utilizar las variables de entorno.
- En primer lugar, la función
os.Environ() []string
nos devuelve un slice de strings con todas las variables de entorno con el formatokey=value
. - Y, en segundo lugar, la función
os.ExpandEnv(s string) string
nos permite reemplazar todas las referencias a variables de entorno de un string con el valor de la variable de entorno.
Usando ficheros .env
Otra manera bastante popular de configurar las variables de entorno es la que se conoce como los ficheros .env
, también
llamados coloquialmente como “dotenv files” o directamente “dotfiles”. De hecho, hoy en día muchas tecnologías soportan
este tipo de ficheros: los propios IDEs, Docker y algunos frameworks web son solo unos pocos ejemplos de ello.
En esta ocasión, en el ecosistema Go también tenemos algunas alternativas para trabajar con este tipo de ficheros. Una
de ellas es la librería godotenv
que se trata de un port de
su versión para Ruby. Para ello, solo tenemos que definir nuestro
fichero .env
del siguiente modo:
DB_HOST=localhost
DB_USER=root
DB_PASS=root
Posteriormente, mediante el uso de la librería anteriormente mencionada podremos hacer:
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
De este modo, nuestra aplicación ya tendrá las variables de entorno presentes en el fichero .env
cargadas y ya podremos usar
las funciones del paquete os
que vimos anteriormente para leerlas. Esta función, además, nos permite pasar el path
a uno o varios ficheros .env
de modo que de esta forma podemos usar otros nombres para nuestros ficheros “dotenv”.
Finalmente, si escribir el pequeño fragmento de código anterior nos da pereza, también tenemos la opción de usar
el autoload
que nos proporciona la librería, el cuál, básicamente, hace uso de la función init()
para ejecutar
la función godotenv.Load()
en tiempo de carga (importación).
import _ "github.com/joho/godotenv/autoload"
Sin embargo, esto no es muy recomendable dado que en esta ocasión sí estaremos obligados a usar .env
como nombre de
nuestro “dotfile” y además no tendremos opción de validar si hubo (o no) algún error durante la carga de dicho fichero.
Usando la librería ‘envconfig’
Además, en el ecosistema Go también podemos encontrar muchas más herramientas que nos pueden resultar útiles a la hora de trabajar con variables de entorno. Si bien todas ellas no dejan de usar (internamente) las funciones que hemos visto hasta ahora, nos pueden resultar muy útiles a la hora de homogéneizar la forma en que trabajamos con la variables de entorno en cada uno de nuestros proyectos.
Una de nuestras favoritas es la que desarrolló hace ya algo de tiempo el popular
Kelsey Hightower, la librería
envconfig
. Con ella, lo que vamos a poder hacer es anotar
nuestros structs de configuración para que, mediante el uso de dicha librería, podamos cargar nuestra configuración
de las variables de entorno. Veamos un ejemplo:
type Config struct {
Debug bool
Port int
Timeout time.Duration
}
Si partimos de la configuración anterior, nos bastaría con ejecutar las siguientes líneas de código para rellenar nuestro struct de configuración con los valores de las variables de entorno:
var config Config
err := envconfig.Process("MYAPP", &config)
if err != nil {
log.Fatal(err.Error())
}
Adicionalmente, como podemos ver, le podemos pasar un prefijo con el nombre de la aplicación, de modo que los nombres de
las variables de entorno los extrapolará de concatenar dicho prefijo con el nombre de los atributos del struct. Por
ejemplo, para el caso anterior, la librería buscaría el valor de las variables de entorno MYAPP_DEBUG
, MYAPP_PORT
y
MYAPP_TIMEOUT
.
Finalmente, también tenemos la opción de usar varios de los tags que nos proporciona la librería para configurar cómo tienen que ser leídas cada una de las variables de entorno (valores por defecto, parámetros requeridos, etc). Algunos de estos tags son:
- Con
envconfig:"variable"
podemos sobreescribir el nombre del atributo del struct por el nombre de variable de entorno que queremos que sea usado en su lugar. - Con
default:"foobar"
podemos definir un valor por defecto para la configuración. - Con
required:"true"
podemos marcar una configuración como requerida, es decir, deberá existir la variable de entorno correspondiente.
Además, en la documentación oficial podemos ver la definición y ejemplos del resto de tags así como los tipos soportados y, para usuarios más avanzados, cómo implementar nuestros propios decoders.
Y vosotros, ¿ya sabíais cómo trabajar con variables de entorno en Go? ¿Tenéis alguna consideración o recomendación que queráis compartir con nosotros? Como siempre, estaremos encantados de recibir vuestro feedback en los comentarios del blog o vía Twitter.