36

What is a good way to set up a project in Scala which uses different configuration depending on environments.

I need to specifically have different databases for development, test and production environment (similar to what is done in Rails)

Daniel Cukier
  • 11,502
  • 15
  • 68
  • 123

3 Answers3

69

Another strategy I'm using consists of using includes. I usually store my DEV settings in the default application.conf file then I create a new conf file for other environments and include the default one.

Let's say my DEV conf application.conf looks like this:

myapp {
    server-address = "localhost"
    server-port = 9000

    some-other-setting = "cool !"
}

Then for the PROD, I could have another file called prod.conf:

include "application"

# override default (DEV) settings
myapp {
    server-address = ${PROD_SERVER_HOSTNAME}
    server-port = ${PROD_SERVER_PORT}
}

Note that I override only the settings that change in the PROD environment (some-other-setting is thus the same as in DEV).

The config bootstrap code doesn't test anything

...
val conf = ConfigFactory.load()
...

To switch from the DEV to the PROD conf, simply pass a system property with the name of the config file to load:

java -Dconfig.resource=prod.conf ...

In DEV, no need to pass it since application.conf will be loaded by default.

So here we're using Typesafe Config's default loading mechanism to achieve this.

I've created a simple project to demonstrate this technique. Feel free to clone and experiment.

danio
  • 8,548
  • 6
  • 47
  • 55
ozeebee
  • 1,878
  • 2
  • 23
  • 26
  • For completeness, I believe the config bootstrap would be: val conf = ConfigFactory.load() def apply() = conf.getConfig("myapp") – mmeyer Jun 08 '16 at 01:46
  • @ozeebee for some reason this is not working. Notice that I added: `fork in run := true` and `java -Dconfig.resource=stg.conf` in build.sbt. File stg.conf is under resources but my config is still coming from application.conf. Notice too that when I use: `val conf = ConfigFactory.load("stg.conf")` then my configurations are loaded per stg.conf. Any idea? – has981 Mar 26 '17 at 09:09
  • @has981 no idea. I've setup a simple [project](https://github.com/ozeebee/test-scala-config) to demonstrate this technique. Feel free to clone and experiment. – ozeebee Mar 27 '17 at 14:13
  • @ozeebee your code is working fine on local but it`s not working when I deploy this code on flink with Docker and Kubernetes – Kallz Mar 13 '20 at 04:00
  • 1
    @Kallz sorry but I don't know flink; Perhaps that this tool has its own configuration mechanism? – ozeebee Mar 20 '20 at 07:15
30

Use typesafe Config. Create a Config object like this:

import com.typesafe.config._

object Config {
  val env = if (System.getenv("SCALA_ENV") == null) "development" else System.getenv("SCALA_ENV")

  val conf = ConfigFactory.load()
  def apply() = conf.getConfig(env)
}

Then create the application.conf file in src/main/resources folder:

development {
  your_app {
    databaseUrl = "jdbc:mysql://localhost:3306/dev_db"
    databaseUser = "xxxx"
    databasePassword = "xxxx"
  }
}
test {
  your_app {
    databaseUrl = "jdbc:mysql://localhost:3306/test_db"
    databaseUser = "xxxxx"
    databasePassword = "xxxx"
  }
}

Now from anywhere in your application, you can access configuration:

Config().getString("your_app.databaseUrl")

If you have your environment set up (e.g. export SCALA_ENV=test) when you run your application, it will consider the right configuration section. The default is development

Daniel Cukier
  • 11,502
  • 15
  • 68
  • 123
  • It took me a while to find this pattern which maintains the primacy of System Properties: ConfigFactory.defaultOverrides().withFallback(config2.getConfig(environment)).withFallback(config2); https://github.com/typesafehub/config/blob/master/examples/java/complex-app/src/main/java/ComplexApp.java – David W Jul 25 '17 at 14:34
2

I wasn't happy with how Daniel Cukiers solution did not allow defaults and overrides, so I changed it around to make full use of those.

The only configuration you have to do is set a ENVIRONMENT variable on the system (defaults to 'dev' if none is set)

(Java solution, compatible with Scala):

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

public class MyCompanyConfig {
    public final static Config base = ConfigFactory.load().getConfig("mycompany");
    public final static String environment = System.getenv("ENVIRONMENT") == null ? "dev" : System.getenv("ENVIRONMENT");

    /**
     * Returns a subtree of the base configuration with environment settings applied.
     *
     * @param setting The subtree to return config for.
     * @return A config with base in given setting, with environment modifications applied.
     */
    public static Config load(String setting) {

        Config config = base.getConfig(setting);

        if (config.hasPath(environment)) {
            return config.getConfig(environment).withFallback(config);
        }

        return config;
    }
}

This allows a single reference.conf in a library looking like this:

mycompany.module1 {
    setting1 : "adefaultvalue"
    url : "localhost"

    test {
        // will be used where ENVIRONMENT="test"
        url : "test.mycompany.com"
    }

    prod {
        // will be used where ENVIRONMENT="prod"
        setting1 : "changethedefault"
        url : "www.mycompany.com"
    }
}

Usage:

Config conf = MyCompanyConfig.load("module1")
DHa
  • 659
  • 1
  • 6
  • 21