2

I have an application which relies on Properties configuration to determine whether to mix in various components or not. For example, the configuration has boolean flags like "componentX.enabled" etc which determine whether these components should be active or not.

Currently I am using these flags in my provider methods like so:

@Provides
@Singleton
@Nullable
public ComponentX provideComponentX(Properties props) {
    if (props.isComponentXEnabled()) {
       return new ComponentX();
    } else {
       return null;
    }
}

@Provides
@Singleton
public Set<Component> provideComponentSet(
   @Nullable ComponentX compX,
   ComponentY compY,
   ComponentZ compZ
) {
   Set<Component> comps = new HashSet<>();
   if (compX != null) {
      comps.add(compX);
   }

   comps.add(compY);
   comps.add(compZ);
   return comps;
}

This approach seems a little clunky (it relies on possible injecting null)- but is there a better way?

The only other way I can think of doing it is by using a parent injector to obtain the application Properties into my module, and then using the set Multibinder.

Then use the create child injector with the new module to complete the bootstrap process.

public class Module extends AbstractModule {
    Properties props;
    public Module(Properties props) {
      this.props = props;
    }

    public void configure() {
        Multibinder<Component> compBinder = Multibinder.newSetBinder(binder(), Component.class);

        if (props.isComponentXEnabled()) {
            compBinder.addBinding().to(ComponentX.class);
        }
        compBinder.addBinding().to(ComponentY.class);
        compBinder.addBinding().to(ComponentZ.class);
    }

}

This also seems a little clunky because it requires the use of a child injector etc.

Again, is there a better way?

Maybe I could use Netflix's Governator (https://github.com/Netflix/governator/wiki/Configuration-Mapping) to inject Configuration values into my module (not sure if that is possible or not)?

How do other people approach this problem?

racc
  • 1,222
  • 10
  • 13
  • It seems like the child injector pattern is pretty common and might be the way to go: http://stackoverflow.com/questions/5504555/guice-is-it-possible-to-inject-modules/12991580#12991580 – racc Jan 08 '15 at 19:55

1 Answers1

2

The applications I've been working with recently have a properties file (or other configuration) that is used to decide which parts of the application are relevant. Our typical approach is parse those properties immediately (just to a Properties object) and construct the application module(s) from that, and they will then conditionally include other modules based on the values specified.

In a couple of places, this has grown into an "init parameters" set, with an enumeration of possible parameters:

enum InitParam {
    PricesQueue("prices.queue")
}

Each enum instance is related to a property key and there is a method to get a basic string value for each parameter from Properties:

boolean presentIn(Properties props) { return props.containsKey(propertyKey); }
String valueIn(Properties props) { return props.getProperty(propertyKey); }

So this can be used like so:

public AppModule extends AbstractModule {
    private final Properties config;
    protected void configure() {
        if (InitParam.PricesQueue.presentIn(config)) {
            install(new PricesQueueConsumerModule(config));
        }
    }
}

Additionally, there is a module to bind all the values in the config properties to String, Optional<String> etc, allowing:

@Inject
public PricesQueueConsumer(@FromInitParam(InitParam.PricesQueue) String queueName) {
}

This will trap the queue consumer being referenced when the configuration isn't available (the module won't bind a string if the value isn't present in the config file) while still allowing the behaviour for when the value isn't present to be deferred to later (by injecting Optional<String> instead)

So this is somewhat similar to your second approach, except that I'd not considered the using-Guice-to-inject-Guice-modules approach, which seems a bit convoluted. Although probably it's essentially the same. Maybe rather than a parent/child injector you could simply create a "bootstrapping" injector to build your top-level application module, and then use that to build a completely separate injector?

araqnid
  • 127,052
  • 24
  • 157
  • 134
  • Yep this seems kind of like the second approach suggested. Your FromInitParam annotation is also a bit like the Configuration annotation provided by Netflix's governator library, correct? I do like the ability to bind config params to an Optional though... – racc Jan 11 '15 at 20:51
  • I haven't used the governator library: but for FromInitParam I cribbed the implementation of the Named annotation provided with Guice and just made it take an enum parameter instead of a string. – araqnid Jan 12 '15 at 12:08