12 Factors: Revisiting the 3rd Factor - Configuration
Tags: programming, devops, productivity, cloud
Published: 2023-03-20 14:55:38

Welcome to my blog!

We continue our journey of studying and reviewing the [12 factors](https://12factor.net) for modern application development, inspired by the books of the legendary Martin Fowler.

**Don't forget to visit [my website](https://luizferreira.dev)** and follow the new posts there. **Contact me through social networks** so we can talk more about these cool topics.

In previous articles, we discussed the first and second factors, [Codebase](https://luizferreira.dev/codebase/) and [Dependency Management](https://luizferreira.dev/dependencies/). Today, we will address the third factor:

## Configurations

The configurations of an application are all the options and parameters that can vary between different environments (development, testing, production, etc.). These parameters include, for example, database connection information, API keys, and access credentials.

## Separate Configurations from Code and Use Environment Variables

One of the main practices of the third factor is to separate configurations from code. This means that configurations should not be stored in the same repository as the application's source code, nor should they be hardcoded directly into the code.

A common and efficient way to store and manage configurations is to use environment variables. Environment variables are a means of storing information outside of the source code, making it easier to maintain and ensuring greater security.

## Benefits

By following the practice of separating configurations from code and using environment variables, you can achieve the following benefits:

1. **Security**: Prevents the leakage of sensitive information, such as API keys and access credentials.
2. **Flexibility**: Allows easy adaptation of the application to different environments, without the need to change the source code.
3. **Versioning**: Avoids the need to version configurations, since they are not part of the application's source code.
4. **Facilitates collaboration**: Facilitates collaboration between developers and teams, as each can have their own local configurations without affecting the environment of other team members.
5. **Simplified deployment**: Facilitates the deployment and configuration of new environments, as configurations can be easily adjusted without the need to change the source code.
6. **Audit and traceability**: Facilitates tracking and auditing configuration changes, as they are managed independently of the code.
7. **Scalability**: Allows for quick adaptation of configurations when the application needs to be scaled, without the need to modify the source code.

## Examples and Tools

In addition to environment variables, there are several tools and libraries available to help with configuration management in different languages and frameworks, such as:

- **dotenv** (Node.js): Loads environment variables from a .env file in your project.

Example of using `dotenv`:

```javascript
// Load the dotenv library
require('dotenv').config();

// Access environment variable
const apiKey = process.env.API_KEY;
```

- **python-dotenv** (Python): Allows the use of `.env` files to store and manage environment variables in Python projects, facilitating the separation between configuration and code, and keeping sensitive configurations out of the version control system.

Example of using `python-dotenv`:

```python
# app.py
import os
from flask import Flask
from dotenv import load_dotenv

load_dotenv()
app = Flask(__name__)
port = os.environ.get('PORT', 5000)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=port)
```

- **Viper** (Golang): Manages application configurations in Go using a variety of sources, such as JSON, TOML, YAML, HCL, among others.

Example of using `Viper`:

```go
package main

import (
	"fmt"
	"github.com/spf13/viper"
)

func main() {
	viper.SetConfigName("config")
	viper.AddConfigPath(".")
	err := viper.ReadInConfig()

	if err != nil {
		panic(fmt.Errorf("Error reading configuration: %s", err))
	}

	apiKey := viper.GetString("api_key")
	fmt.Println("API Key:", apiKey)
}
```

- **config-rs** (Rust): Configuration library for Rust that supports JSON, TOML, YAML, HJSON, and INI files, among others.

Example of using `config-rs`:

```rust
use config::{Config, File, Environment};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Settings {
    api_key: String,
}

fn main() {
    let mut settings = Config::default();

    settings
        .merge(File::with_name("config"))
        .unwrap()
        .merge(Environment::with_prefix("APP"))
        .unwrap();

    let settings: Settings = settings.try_into().unwrap();
    println!("API Key: {:?}", settings.api_key);
}
```

## Conclusion

In this article, we covered the third factor of the 12 factors of software development, which is configuration management. Following the practices presented here can help ensure the security, flexibility, and scalability of your application.

**Stay tuned for the next articles**, where we will continue our journey through the 12 factors, delving even deeper into the concepts and practices that make them up.

Until next time!