loading...

Java 8 Type-Safe Configuration with default methods

cchacin profile image Carlos Chacin ☕👽 Originally published at cchacin.github.io on ・2 min read

There is a common requirement in most of the Java/JavaEE projects related to how to differentiate environment configurations like development, test, QA, production, etc. for that reason there are a lot of frameworks or libraries to resolve that:

Even there is a Java Specification Request (JSR) Proposal:

How those libraries resolve the problem

Most of those libraries require to have an injection point(s) for the configurations and also the source of the configuration(s), something like this:

dev.properties:

host=localhost
port=5432
schema=public

prod.properties:

host=production.com
port=5432
schema=public

ConfigurationResolver.java:

class ConfigurationResolver implements SomeLibraryInterface {
  @Override
  public String resolvePropertyFilename() {
    // probably do some trick to load a global/base configuration
    // if is not provided for the library
    return String.format("%s.properties", System.getProperty("MY_ENV", "dev"));
  }
}

MyType.java:

class MyType {
  @Configuration(key = "host")
  String host;
  @Configuration(key = "port")
  int port;
  @Configuration(key = "schema", defaultValue = "public")
  String schema;
}

Notice that all the granularity is only needed because of lack of support for custom types

Besides that, with the new Java 8 capabilities, specifically with default methods and some utility classes are easy to get this behavior.

With Java 8 default methods

DefaultConfiguration.java:

interface DefaultConfiguration {
  default DataSource datasource {
    // create the DS with host, port and schema
    return new MyDataSource("localhost", 5432, "public");
  }
}

DevConfiguration.java:

class DevConfiguration implements DefaultConfiguration {
  // No need to override methods if they are the same
}

ProdConfiguration:

class ProdConfiguration implements DefaultConfiguration {
  default DataSource datasource {
    return new MyDataSource("production.com", 5432, "public");
  }
}

ConfigurationFactory.java:

abstract class ConfigurationFactory {
    private static final Map<String, DefaultConfiguration> configurations = new HashMap<>();

    static {
        configurations.put("development", new DevConfiguration());
        configurations.put("production", new ProdConfiguration());
    }

    public static DefaultConfiguration configuration() {
        return configurations.getOrDefault(System.getProperty("MY_ENV"), new DevConfiguration());
    }
}

Notice that you can use an enumeration instead of a map as a configuration factory.

Pros

  • Typesafe configurations
  • Support for all kinds of types not only String, Date or primitives. you can specify or example DataSource, TimeUnits, JedisPool, etc.
  • No conversion/mapping from strings to types
  • No more .properties, .xml or .yml files
  • Possibility to use other sources to fill the properties like properties files, database, rest API's, etc.
  • No library/framework/dependency required in your app

Cons

  • Changes in the configuration require to compile/deploy (this shouldn’t be a problem in the 99% of the cases)

source code

Discussion

pic
Editor guide