Configuration and Pipes.
Introduction
Let’s say that you have microservices that you don’t want to start serving content unless certain environment variables have been defined.
Recently, we had to implement such a solution for FizzBuzz Pro
In our case, we needed to make sure that the fizz-crypto
microservice did not start unless the following variables were defined in the system.
FIZZ_PORT_SVC_CRYPTO
FIZZ_JWT_KEY
FIZZ_RANDOM_BYTE_LENGTH
FIZZ_BCRYPT_HASH_ROUNDS
FIZZ_AES_PASSPHRASE
How do we make sure that the service does not start if any of these environment variables are missing? And how can we do this with the minimal amount of maintenance overhead?
Introducing the fizz-env
Module
To begin, let’s create a module for our purposes. We’ll publish our module to github.com/zerotohero-dev/fizz-env
private git repository:
# Switch to our workspace:
cd $WORKSPACE
# `fizz-env` is an empty repository:
git clone git@github.com:zerotohero-dev/fizz-env.git
# Switch to the project folder:
cd fizz-env
# Initialize our module:
go mod init github.com/zerotohero-dev/fizz-env
This will result in the following go.mod
file that defines our module:
module github.com/zerotohero-dev/fizz-env
go 1.16
Then let’s create a ./pkg/env/fizz.go
file that we’ll work on:
cd $WORKSPACE/fizz-env
mkdir -p pkg/env
touch pkg/env/fizz.go
Then let’s import the packages that we’ll need:
// $WORKSPACE/fizz-env/pkg/env/fizz.go
import (
"fmt"
"os"
"reflect"
)
The FizzEnv
Struct
To reduce potential errors and typos, it’s best to define the environment variables that we need using a struct that holds these variables as fields.
We’ll name our struct FizzEnv
:
// $WORKSPACE/fizz-env/pkg/env/fizz.go
package env
type envCrypto struct {
PortSvcCrypto string
JwtKey string
RandomByteLength string
BcryptHashRounds string
AesPassphrase string
}
type FizzEnv struct {
Crypto envCrypto
}
So, for example, if env
is a FizzEnv
, then env.Crypto.JwtKey
will hold the value of $FIZZ_JWT_KEY
environment variable.
The Factory Function
Let’s also create a factory function to create an instance of FizzEnv
and populate the related field values from the system’s environment:
// $WORKSPACE/fizz-env/pkg/env/fizz.go
func New() *FizzEnv {
res := &FizzEnv{
Crypto: envCrypto{
PortSvcCrypto: os.Getenv("FIZZ_PORT_SVC_CRYPTO"),
JwtKey: os.Getenv("FIZZ_JWT_KEY"),
RandomByteLength: os.Getenv("FIZZ_RANDOM_BYTE_LENGTH"),
BcryptHashRounds: os.Getenv("FIZZ_BCRYPT_HASH_ROUNDS"),
AesPassphrase: os.Getenv("FIZZ_AES_PASSPHRASE"),
Environment: os.Getenv("FIZZ_ENV"),
},
}
return res
}
Sanitizing the Environment Variables
Finally, let’s create a receiver function that traverses and makes sure that all of the environment variables that we need have been set:
// $WORKSPACE/fizz-env/pkg/env/fizz.go
func (e FizzEnv) SanitizeCrypto() {
v := reflect.ValueOf(e.Crypto)
for i := 0; i < v.NumField(); i++ {
val, name := v.Field(i).String(), v.Type().Field(i).Name
if val == "" {
panic(
fmt.Sprintf(
"The environment variable that maps to '%s' " +
"is not defined.", name,
),
)
}
}
}
Since there could be any number of fields in our struct, we had to use reflection to iterate across the struct fields to keep our code maintainable by following the open-closed principle.
And that’s pretty much it.
Whenever any of the required environment variables are not defined, calling env.SanitizeCrypto()
on a FizzEnv
instance env
will cause the application to crash real loud real fast, which you would prefer rather than the app running silently despite the necessary environment information that it needs being missing.
Aside
Failing early, and failing loudly is also known as the fail-fast principle in software engineering. The fail-fast principle means that you should stop the current operation as soon as any unexpected error occurs.
Surprisingly, this approach results in a more stable solution in the long run. Why so? Because when you follow this principle, you tighten the feedback loop: Instead of suppressing errors and sweeping things under the rug, you quickly reveal the defects and fix the failures as early as possible.
In the end, you’ll benefit from this approach greatly.
Our Solution in Action
Let’s use this in a microservice to see our solution in action:
# Switch to our workspace:
cd $WORKSPACE
# `fizz-crypto` is an empty repository:
git clone git@github.com:zerotohero-dev/fizz-crypto.git
# We'll create our files in here:
cd fizz-crypto
# Initialize our module:
go mod init github.com/zerotohero-dev/fizz-crypto
This will result in the following go.mod
file that defines our module:
module github.com/zerotohero-dev/fizz-env
go 1.16
Now, let’s go get
our dependency:
go get github.com/zerotohero-dev/fizz-env
And we can use our new fizz-env/pkg/env
as follows:
// $WORKSPACE/fizz-crypto/main.go
package main
import (
"fmt"
"github.com/zerotohero-dev/fizz-env/pkg/env"
)
func main() {
// Populate an `env.FizzEnv` collection buy parsing the system
// environment variables:
e := env.New()
// Make sure that all of the environment variables that
//`fizz-crypto` needs have been defined; panic otherwise.
e.SanitizeCrypto()
// Print the value of an environment variable.
fmt.Println("key", e.Crypto.JwtKey)
}
Since we haven’t defined any environment variables yet, running the above code on my system results in the following panic as expected:
panic: The environment variable that corresponds to 'PortSvcCrypto'
is not defined.
goroutine 1 [running]:
github.com/zerotohero-dev/fizz-env/pkg/env.FizzEnv.
SanitizeCrypto(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
/Users/volkan/go/pkg/mod/github.com/zerotohero-dev/
fizz-env@v0.1.0/pkg/env/fizz.go:40 +0x30f
main.main()
/Users/volkan/Desktop/PROJECTS/fizz-crypto/main.go:22 +0x78
Conclusion
In this article, we’ve seen an approach to ensure that all of the environment variables that a service needs have been defined before the service starts.
We will use this fizz-env
module in all of the FizzBuzz Pro microservices that we’ll be creating.
I’ll also share any improvement to this fizz-env
module that’s worth mentioning as we develop it.
Read the Source
Below, you’ll find the zip archives of the projects and other related artifacts that we’ve covered in this article.
Enjoy… And may the source be with you 🦄.
Section Contents
▶ How to Ensure Environment Variables Are Set