Tobiasz’s blog

I am writing about Go and cloud-native technologies.

Configure Go app using envconfig and struct embedding

Environment variables are great. If you are not using them to pass configuration into your app, you should take a look at 12 Factor App - config, it’s explained easily and clearly.

Reading environment variables in Go is very simple:

import (
	"fmt"
	"os"
)

func main() {
	fmt.Printf("Debug env is set to: '%s'\n", os.Getenv("USE_DEBUG"))
}

We can do better!

os.Getenv is a great building block, however often you need to read 10+ variables in your application. Building config struct out of it using os package results in a lot of boilerplate code. Additional complexity is added when you want to parse other types than string.

For all microservices that I am building in Go I am using kelseyhightower/envconfig. It is very straightforward, let’s take a look at some code.

type config struct {
	Timeout            time.Duration `default:"5m" `
	Debug              bool
	SupportedCountries []string      `envconfig:"SUPPORTED_COUNTRIES"`
}

func main() {
	var cfg config
	if err := envconfig.Process("", &cfg); err != nil {
		log.Fatal(err)
	}
	log.Printf("%+v\n", cfg)
}

Envconfig decodes environment variables into provided struct. By default envoconfig will update only the fields which has corresponded environment variables.

You can specify a more accurate name on env side using struct tags envconfig:"MY_ENV". Using struct tags you can also define required and default values.

My favorite part - struct embedding

If you build a lot of microservices, it will result in a lot of internal libraries that you share among services. Often you need to pass config to them and you would like to change it via environment variables.

Using envconfig and struct embedding it is really easy.

Let’s create our internal package sftp and define the following struct:

package sftp

type Config struct {
	Username  string   `envconfig:"SFTP_USERNAME"`
	Password  Password `envconfig:"SFTP_PASSWORD"`
	Hostname  string   `envconfig:"SFTP_HOSTNAME"`
	Directory string   `envconfig:"SFTP_DIRECTORY"`
}

type Password string

func (Password) String() string {
	return "***"
}

Now you can import it in you microservice by embedding sftp.Config into config struct that you already have there. You can also define it as new type if you want to import more then 1 config struct.

type config struct {
	Timeout            time.Duration `default:"5m" `
	Debug              bool          `required:"true"`
	SupportedCountries []string      `envconfig:"SUPPORTED_COUNTRIES"`
	SftpConfig
}

type SftpConfig sftp.Config

If you provide sftp config via environment variables and parse main cfg you will see that config is populated correctly and you can pass cfg.SftpConfig into your sftp package. Using that approach you can tweak the config of your internal library just by using env variables.

Summary

kelseyhightower/envconfig is a very simple and powerful library that helps you to parse environment variables into struct. It supports parsing into multiple data types and you can define defaults and required parameters.

If you are using some internal libraries you can pass config to them using envconfig by adding library config struct into your main configuration using struct embedding. You can find an example code at my github.