Ratpacked: Externalized Application Configuration
Ratpack has very useful methods to apply application configuration in our application. We can read configuration properties from files in different formats, like JSON, YAML and Java properties, and the files can be read from different locations, like class path or file system. We can also set configuration properties via Java system properties on the command line or use environment variables.
We use the ratpack.config.ConfigData
class with the static of
method to add configuration properties to our application. We provide a lambda expression or Groovy closure to the of
method to build our configuration. Here we specify external files, locations and other configuration options we want to include for our application. If the same configuration property is defined in multiple configuration sources Ratpack will apply the value that is last discovered. This way we can for example provide default values and allow them to be overridden with environment variables if we apply the environment variables last.
To use the values that are gathered we use the get
method of the ConfigData
instance. We can apply the configuration properties to the properties of a configuration class that is then automatically instantiated. We add this to the registry so we can use the configuration properties further down in our application.
In the following example Ratpack application we use different ways to apply configuration properties. It is inspired by how Spring Boot reads and applies externalized configuration properties. First we use a simple Map
with default values, then the class path is scanned for files with names application.yml
, application.json
and application.properties
in either the root of the class path or a config
package. Next the same file names are searched on the file system relative from where the application is started. Next Java system properties starting with sample.
are applied to the configuration. And finally environment variables starting with SAMPLE_
are interpreted as configuration properties.
Let's start with the simple configuration class and properties we want to set via Ratpack's configuration ability:
// File: src/main/groovy/com/mrhaki/SampleConfig.groovy
package com.mrhaki
/**
* Configuration properties for our application.
*/
class SampleConfig {
/**
* URL for external service to invoke with HTTP client.
*/
String externalServiceUrl
/**
* URI to access the Mongo database.
*/
String mongoUri
/**
* Indicate if we need to use a HTTP proxy.
*/
boolean useProxy
/**
* Simple message
*/
String message
}
Next we have a very simple Ratpack application. Here we use ConfigData.of
with a lot of helper methods to read in configuration properties from different sources:
// File: src/ratpack/Ratpack.groovy
import com.google.common.io.Resources
import com.mrhaki.SampleConfig
import ratpack.config.ConfigData
import ratpack.config.ConfigDataBuilder
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import static ratpack.groovy.Groovy.ratpack
ratpack {
bindings {
final ConfigData configData = ConfigData.of { builder ->
// Set default value, can be overridden by
// configuration further down the chain.
// The map must have String values.
builder.props(['app.useProxy': Boolean.TRUE.toString()])
loadExternalConfiguration(builder)
// Look for system properties starting with
// sample. to set or override configuration properties.
builder.sysProps('sample.')
// Look for environment variables starting
// with SAMPLE_ to set or override configuration properties.
builder.env('SAMPLE_')
builder.build()
}
// Assign all configuration properties from the /app node
// to the properties in the SampleConfig class.
bindInstance(SampleConfig, configData.get('/app', SampleConfig))
}
handlers {
get('configprops') { SampleConfig config ->
render(prettyPrint(toJson(config)))
}
}
}
private void loadExternalConfiguration(final ConfigDataBuilder configDataBuilder) {
final List configurationLocations =
['application.yml',
'application.json',
'application.properties',
'config/application.yml',
'config/application.json',
'config/application.properties']
configurationLocations.each { configurationLocation ->
loadClasspathConfiguration(configDataBuilder, configurationLocation)
}
configurationLocations.each { configurationLocation ->
loadFileSystemConfiguration(configDataBuilder, configurationLocation)
}
}
private void loadClasspathConfiguration(
final ConfigDataBuilder configDataBuilder,
final String configurationName) {
try {
final URL configurationResource = Resources.getResource(configurationName)
switch (configurationName) {
case yaml():
configDataBuilder.yaml(configurationResource)
break
case json():
configDataBuilder.json(configurationResource)
break
case properties():
configDataBuilder.props(configurationResource)
break
default:
break
}
} catch (IllegalArgumentException ignore) {
// Configuration not found.
}
}
private void loadFileSystemConfiguration(
final ConfigDataBuilder configDataBuilder,
final String configurationFilename) {
final Path configurationPath = Paths.get(configurationFilename)
if (Files.exists(configurationPath)) {
switch (configurationFilename) {
case yaml():
configDataBuilder.yaml(configurationPath)
break
case json():
configDataBuilder.json(configurationPath)
break
case properties():
configDataBuilder.props(configurationPath)
break
default:
break
}
}
}
private def yaml() {
return hasExtension('yml')
}
private def json() {
return hasExtension('json')
}
private def properties() {
return hasExtension('properties')
}
private def hasExtension(final String extension) {
return { filename -> filename ==~ /.*\.${extension}$/ }
}
Next we create some external configuration files:
# File: src/ratpack/application.yml
---
app:
mongoUri: mongodb://mongo:27017/test
# File: src/ratpack/application.properties
app.externalServiceUrl = http://remote:9000/api
app.message = Ratpack rules!
Let's run the application and see the output of the configprops
endpoint:
$ http localhost:5050/configprops
...
{
"externalServiceUrl": "http://remote:9000/api",
"useProxy": true,
"message": "Ratpack rules!",
"mongoUri": "mongodb://mongo:27017/test"
}
Next we stop the application and start it with the Java system property -Dsample.app.useProxy=false
and the environment variable SAMPLE_APP__MESSAGE='Ratpack rocks!'
. We check the configprops
endpoint again:
$ http localhost:5050/configprops
...
{
"externalServiceUrl": "http://remote:9000/api",
"useProxy": false,
"message": "Ratpack rocks!",
"mongoUri": "mongodb://mongo:27017/test"
}
Written with Ratpack 1.0.0.
This is the 1000th blog post on the Messages from mrhaki blog. I've blogged about many different subjects, starting with Apache Cocoon, Netbeans, followed by Groovy and Groovy related technologies like Grails, Gradle. Also about other developer subjects like Asciidoctor and much more. I hope you enjoyed the first 1000, because I am not finished and will continue with more blog posts about great technologies.