Loading...

January 14, 2016

Ratpacked: Using Database As Custom Configuration Source

We learned about externalised configuration in a previous blog post. Ratpack provides support out of the box for several formats and configuration sources. For example we can use files in YAML, properties or JSON format, arguments passed to the application, system properties and environment variables. We can add our own configuration source by implementing the ratpack.config.ConfigSource interface. We must override the method loadConfigData to load configuration data from a custom source and convert it to a format that can be handled by Ratpack.

We are going to write a custom ConfigSource implementation that will get configuration data from a database. We assume the data is in a table with the name CONFIGURATION and has the columns KEY and VALUE. The format of the key is the same as for Java properties files.

package com.mrhaki.config

import groovy.sql.Sql
import ratpack.config.ConfigSource
import ratpack.config.internal.source.AbstractPropertiesConfigSource

/**
 * {@link ConfigSource} implementation to read configuration 
 * data from a database table. The database must have a table
 * CONFIGURATION with the VARCHAR columns KEY and VALUE.
 * The format of the key matches that of regular Java properties.
 * 
 * E.g. we can insert the configuration key 'app.message' like this:
 * INSERT INTO CONFIGURATION(KEY, VALUE) VALUES('app.message', 'Ratpack rocks');
 * 
 * This class extends {@link AbstractPropertiesConfigSource}, because it supports
 * property key formats like we use in our database.
 */
class JdbcConfigSource extends AbstractPropertiesConfigSource {

    /**
     * Database JDBC url, username, password and driver.
     */
    final Map<String, String> sqlProperties

    JdbcConfigSource(final Map<String, String> sqlProperties) {
        super(Optional.empty())
        this.sqlProperties = sqlProperties
    }

    @Override
    protected Properties loadProperties() throws Exception {
        final Properties properties = new Properties()
        
        Sql.withInstance(sqlProperties) { sql->
            // Get key/value pairs from database.
            sql.eachRow("SELECT KEY, VALUE FROM CONFIGURATION") { row ->
                // Save found data in Properties object.
                properties.setProperty(row.key, row.value)
            } 
        } 

        return properties
    }
}

To use this ConfigSource implementation in our Ratpack application we use the add method of ConfigDataBuilder:

import com.mrhaki.config.JdbcConfigSource
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import ratpack.config.ConfigData

import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import static ratpack.groovy.Groovy.ratpack

class SampleConfig {
    String message
}

ratpack {

    bindings {
        final ConfigData configData = ConfigData.of { builder ->
            // Add our custom JdbcConfigSource as 
            // configuration source.            
            builder.add(
                        new JdbcConfigSource(
                            driver: 'org.postgresql.Driver',
                            url: 'jdbc:postgresql://192.168.99.100:32768/',
                            user: 'postgres',
                            password: 'secret'))
                   .build()
        }

        // Assign all configuration properties from the /app node
        // to the properties in the SampleConfig class.
        bindInstance(SimpleConfig, configData.get('/app', SampleConfig))
    }

    handlers {
        get('configprops') { SampleConfig config ->
            render(prettyPrint(toJson(config)))
        }
    }

}

When we request configprops we get the following result:

$ http -b localhost:5050/configprops
{
    "message": "Ratpack rocks!"
}

$ 

Written with Ratpack 1.1.1.