Live configuration for Java applications #hubspot-open-source
Java
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
src
.gitignore
LICENSE.txt
README.md
pom.xml

README.md

LiveConfig

Live configuration for Java projects.

Basic Usage

LiveConfig gives you access to configuration parameters that can be loaded from a variety of sources. You create a LiveConfig like so...

// a simple config, backed by a map
Map<String, String> propertyMap = Maps.newHashMap();
LiveConfig config = LiveConfig.fromMap(propertyMap);

You can create more useful configurations by adding more sources. This example will first look for configuration parameters in env vars, falling back to system properties and finally to default properties that are included on your classpath.

LiveConfig config = LiveConfig.builder()
    .usingEnvironmentVariables()
    .usingSystemProperties()
    .usingPropertiesFile("/etc/hubspot.properties")
    .usingDefaultProperties("com.hubspot")
    .build();

The LiveConfig class exposes simple getters such as getInt(String key) which can be used to access properties from your backing stores. There are getters for most primitive types, as well as getters that expose Optional values so you can gracefully handle missing values in your code. Here are some examples...

// basic getters
String name = config.getString("first.name");
int count = config.getInt("widget.count");
boolean on = config.getBoolean("lights.on");
long expiration = config.getLong("expiration.time");
List<String> users = config.getList("user.names");

// optionals
String name = config.getStringMaybe("first.name").orNull();
int count = config.getIntMaybe("widget.count").or(0);

// getters with fallbacks
String name = config.getString("full.name", "first.name");

Value and Live Values

One of the coolest features of LiveConfig is LiveValues. These make it very easy to use configuration parameters to your application that get updated in realtime. Like the other LiveConfig getters, these also come in flavors for most primitive types. Here are some examples...

Value<String> name = config.getLiveString("first.name");
name.get(); // Trevor
... // change 'first.name' in your backing store
name.get(); // James

// you can use these like Optionals too
Value<Boolean> on = config.getLiveBoolean("lights.on");
lights.enable(on.or(false));

In general it's better to use Value<T> in your method signatures rather than the concrete LiveXXX classes, so we made some shortcuts to facilitate testing in your apps. You can use FixedValue to quicky wrap constants without having to deal with LiveValues or LiveConfig.

Value<Boolean> on = FixedValue.of(true);
on.get(); // true

Value<Boolean> on = FixedValue.absent();
on.get(); // throws IllegalStateException
on.or(false); // false;

Guice

Using Guice with LiveConfig is very easy. Simply use the provided LiveConfigModule and bindings will be created for all properties enumerated by the specified LiveConfig instance.

Map<String, String> properties = Maps.newHashMap();
properties.put("first.name", "Trevor");
properties.put("widget.count", "8");
properties.put("lights.on", "true");

LiveConfig config = LiveConfig.fromMap(properties);
Injector injector = Guice.createInjector(new LiveConfigModule(config));
Widgets widgets = injector.getInstance(Widgets.class);
widgets.countWidgets(); // 8

// ... elsewhere
class Widgets {
    private Value<Integer> widgetCount;

    @Inject
    Widgets(@Named("widget.count") Value<Integer> widgetCount) {
      this.widgetCount = widgetCount;
    }

    int countWidgets() {
      return widgetCount.get();
    }
}

Advanced Usage

Static maps by themselves are not very useful, so LiveConfig has out of the box support for environment variables, system properties and properties files located on your filesystem. The real fun begins when you hook LiveConfig up to custom resolver that can fetch configuration from an external service, like a database or an API. Here's a simple example...

class ConfigApiResolver implements Resolver {
    private final AtomicReference mapReference;
    private ScheduledExecutorService executor;

    public ConfigApiResolver() {
        mapReference = new AtomicReference(fetchMap());

        // update config every minute
        executor = Executors.newScheduledThreadPool(1);
        executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                mapReference.set(fetchMap());
            }
        }, 1, 1, TimeUnit.MINUTES);
    }

    @Override
    public Optional<String> get(String key) {
        return Optional.fromNullable(mapReference.get().get(key));
    }

    @Override
    public Set<String> keySet() {
        return mapReference.get().keySet();
    }

    private Map<String, String> fetchMap() {
      // ... hit your api here
    }
}

You can add your custom resolver to the chain like so...

LiveConfig config = LiveConfig.builder()
    .usingEnvironmentVariables()
    .usingSystemProperties()
    .usingResolver(new ConfigApiResolver()) // <-- your new resolver
    .usingDefaultProperties("com.hubspot")
    .build();