AWS CDK: Per-Environment Configuration Patterns
Tags: aws, cdk, iac
Published: 2021-05-26 12:23:13

# Introduction 👋
Often projects will have different configuration values for each deployed environment. This could be feature toggles, URLs for third-party services or any number of other variables.

With the AWS CDK, this is simple to configure. I have seen a few different approaches to this problem. In this blog I'll share a few suitable for use in TypeScript.


# Method 1️⃣: Stack Configuration Function
This approach uses a mapper function to return the configuration, you can see we have a single configuration property defined in the `EnvironmentConfig` interface.


```typescript
interface IEnvironmentConfig {
  readonly myEnvSpecificApiEndpoint: string;
}

const environmentConfig = (environment: string): IEnvironmentConfig => {
  const environmentMapper: {
    [environment: string]: {
      myEnvSpecificApiEndpoint: string;
    };
  } = {
    local: {
      myEnvSpecificApiEndpoint: 'https://dev.google.com/api',
    },
    test: {
      myEnvSpecificApiEndpoint: 'https://test.google.com/api',
    },
    production: {
      myEnvSpecificApiEndpoint: 'https://google.com/api',
    },
  };
  return environmentMapper[environmentName];
};
```

This function would be called from the stack like this, `process.env.ENV_NAME` would correspond to the environment name (replace this with your environment name variable for your chosen CI/CD pipeline) or default to local if undefined.
```typescript
const environment: string = process.env.ENV_NAME || 'local';
const envConfig: IEnvironmentConfig = environmentConfig(environment);
```

You can then access the configuration like this:
```typescript
const apiEndpoint: string = envConfig.myEnvSpecificApiEndpoint;
```
The `apiEndpoint` variable is now ready to be used in your stack.

# Method 2️⃣: CDK Runtime Context
[CDK Context](https://docs.aws.amazon.com/cdk/latest/guide/context.html) values are key-value pairs that can be associated with a stack or construct. There are a number of different ways these values can be configured, for more information on that see the link above to the documentation.

In this example, I'm going to use `cdk.context.json` file in the root of a CDK project to configure a stack.

Here is an example `cdk.context.json`:

```json
{
   "local":{
      "myEnvSpecificApiEndpoint":"https://dev.google.com/api"
   },
   "test":{
      "myEnvSpecificApiEndpoint":"https://test.google.com/api"
   },
   "production":{
      "myEnvSpecificApiEndpoint":"https://google.com/api"
   }
}
```
Interfaces can also be created to define the type of configuration data expected:
```typescript
interface IEnvironmentConfig {
  readonly myEnvSpecificApiEndpoint: string;
}
```

These values can be accessed like this:
```typescript
const environment: string = process.env.ENV_NAME || 'local';
const envConfig: IEnvironmentConfig = scope.node.tryGetContext(environment);
const apiEndpoint: string = envConfig.myEnvSpecificApiEndpoint;
```

# Method 3️⃣: Extending StackProps
The AWS CDK [StackProps](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.StackProps.html) interface can be extended to add additional configuration properties. In this example we will extend the AWS CDK interface to add a property called `myEnvSpecificApiEndpoint`.

```typescript
interface IEnvironmentConfig extends StackProps {
  readonly myEnvSpecificApiEndpoint: string;
}
```

Now in the stack initialisation file (located in the `bin` directory) we can pass this in.

```typescript
const app = new cdk.App();
new TheScheduledLambdaStack(app, 'TheScheduledLambdaStack',{
    myEnvSpecificApiEndpoint: 'https://dev.google.com/api'
});
```
Now, the one downfall of this is that you still need to implement something like method 1 or 2 to configure it on a per-environment basis. This would look something like this for method 1:

```typescript
new TheScheduledLambdaStack(app, 'TheScheduledLambdaStack',{
    myEnvSpecificApiEndpoint: envConfig.myEnvSpecificApiEndpoint;
});
```
# Conclusion 🤌

Looking at all three methods, I personally like method 2. Until recently I was blissfully unaware the CDK had already 'solved' this problem for us.

I live by the saying 'code is a liability' - the less code you manage the better.

Do you..
- use any of these methods already?
- have a better way of doing it?
- have a different opinion on the optimal solution?

Let me know in the comments!