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)
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)
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.
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
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")
Here is a solution that is in Scala, allows for overrides, and doesn't rely on an external library.
object Config {
var test: Map[String, String] = {
Map(
"libsvmData" -> new java.io.File("./src/test/resources/sample_libsvm_data.txt").getCanonicalPath,
"somethingElse" -> "hi"
)
}
var production: Map[String, String] = {
Map(
"libsvmData" -> "s3a://my-cool-bucket/fun-data/libsvm.txt",
"somethingElse" -> "whatever"
)
}
var environment = sys.env.getOrElse("PROJECT_ENV", "production")
def get(key: String): String = {
if (environment == "test") {
test(key)
} else {
production(key)
}
}
}
If PROJECT_ENV
is set to test
, Config.get("somethingElse")
will return "hi"
. Otherwise, it will return "whatever"
.
Running PROJECT_ENV=test sbt test
will get old fast, so you can have SBT set the environment variable when the test suite is run.
fork in Test := true
envVars in Test := Map("PROJECT_ENV" -> "test")
Here is how to override the existing config.
Config.test = Config.test ++ Map("somethingElse" -> "give me clean air")
Here's a link to the full blog post I wrote on this topic.