Make your environment variables more robust by making them more fragile

by Kyriakos Chatzidimitriou | Jan 29, 2019 08:39 | tutorials

javascriptnodepatternsenvironment variablesdevitdevelopment

In the Devit 2016 conference in Thessaloniki, in one of the keynotes, Yegor Bugayenko explained why you need to make your software more fragile, in order to make it more robust. It was a really good talk, a talk that I still remember. In the end of this post I have embedded the recording from the conference.

In this talk Yegor Bugayenko explained why you need to fail fast software in development in order to make it more robust in production. According to that strategy, every time there is a potential source of a bug or in general something that is not in the happy path, we should make it more visible and "bigger", in order to catch it by failing fast, fix it and deploy again. Some of the examples in the talk say "do this":

@Override
void save() {
throw new Exception(
"not implemented yet"
);
}


@Override
void save() {
// not implemented yet
}


So if the method is not implemented, the program will fail fast with the first strategy but at least you will not think you called save() and it did something when it actually didn't, like in the second example. Or, "do this":

file.delete();


if(!file.delete()) {
throw new Exception(
"failed to delete a file"
);
}


In this case we ignore the output of the method. If for some reason the system fails to delete the file, we are left hanging with the idea that the file was deleted. But if we throw the exception, we can start writing exception handling code on how to approach a failed file deletion (file not found? file in use? etc.).

Now to our case, according to the third factor of the twelve-factor-app methodology, we should use environment variables in order to configure the code between deploys (i.e. environments like staging, production, development etc.).

In a lot of example in node.js that use environment variables there is often the pattern:

const env = process.env.NODE_ENV || "development";


or

const mongo_uri = process.env.MONGODB_URI || "mongodb://localhost:27017/test";


Based on the "making the software more fragile in order to make it more robust" strategy, in my projects I use the following pattern:

function throwErr(msg) {
throw new Error(msg);
}

const env = process.env.NODE_ENV || throwErr('NODE_ENV is unset');

const mongo_uri = process.env.MONGODB_URI || throwErr('MONGODB_URI is unset');


So now, all unset environment variables will cause an error to be thrown and this will force me (or other members of the team) to set them up before continuing with the deployment to an environment, even my own development environment. And how will I know what to do? It will be shown in the logs.

Below is the talk that inspired me for this pattern:

Personal note: This is a post I wanted to write for so much time (a couple of years), but after reading Kent C. Dodds newsletter yesterday on "Intentional Career Building" I did it first thing in the morning as one of the call to action bullet points he suggested: "Write the blog post you wish existed last week when you were learning something new"