Atom Feed SITE FEED   ADD TO GOOGLE READER

Elite Guice 2: Binding de-duplication

In part one, I showed how to initialize a Guice extension using the @Inject tag on a method. In this post I'm going to demonstrate a Guice for deduplicating bindings.

What is binding duplication?


One of the features of the Multibindings Extension is that there's no central coordination. Each module contributes its own bindings, and they all get amalgamated into a single collection. Suppose you have these modules:
public class CandybarModule extends AbstractModule {
protected void configure() {
Multibinder<Snack> multibinder = Multibinder.newSetBinder(binder(), Snack.class);
multibinder.addBinding().toInstance(new Twix());
multibinder.addBinding().toProvider(SnickersProvider.class);
}
}
public class ChipsModule extends AbstractModule {
protected void configure() {
Multibinder<Snack> multibinder = Multibinder.newSetBinder(binder(), Snack.class);
multibinder.addBinding().to(Pringles.class);
multibinder.addBinding().toInstance(new Doritos());
}
}

Out of this API, our Multibinder implementation needs to bind Set<Snack> exactly once. This is tricky! For example, suppose our newSetBinder method was implemented like this:
  public static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
binder.bind(getSetKey(type)).toProvider(getSetProvider(type));
return new Multibinder(type);
}

This won't work. We call newSetProvider from both the CandybarModule and the ChipsModule, and Guice complains because we're binding Set<Snack> twice. This is binding duplication.

If Guice had an duplicate-binding API


Guice doesn't have this API, but what we kind of find ourselves wanting is a way to query the binder for its bindings thus-far:
  public static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
if (!binder.getBindings().containsKey(getSetKey(type))) {
binder.bind(getSetKey(type)).toProvider(getSetProvider(type));
}
return new Multibinder(type);
}

This would work, but in general it's a pretty terrible idea. It makes it easy to write fragile modules - modules that depend on the order they are installed in. And it also changes the module from being static configuration (good!) to a dynamic-runtime configuration (bad!). So we're glad this API doesn't exist.

But in this one case, we really wish we could prevent our binding from being performed twice.

Modules as value objects


The solution to this problem is a bit surprising, but I think it's quite elegant. Guice conveniently allows the same module to be installed twice. This is necessary so that both your PizzaServletsModule and your PizzaDatabaseModule can install your PizzaDomainModule.

Rather than binding the Set<Snack> directly on the binder, we create special Module class whose only job is to create that binding. And then we give that module a proper equals() and hashCode() methods, so that any two instances that bind the same type are equal.

Our final code looks like this:
  public static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
binder.install(new MultibindingModule(getSetType(), getProviderType()));
return new Multibinder(type);
}

The Module trick allows us to decentralize the binding. And that means less configuration code, and that means less code. Hooray for Guice.