Configuration de-duplication

Guice modules, bundles and dropwizard bundles allow registration of multiple instance of the same type. For example:

.modules(new MyModule("one"), new MyModule("two"))

Note

Before, guice did not allow to register multiple modules of the same type, but multiple instances support is more logical in context of dropwizard, because dropwizard itself allows registration of multiple bundles of the same type.

Duplicates

But in some cases it is desirable to avoid such registrations. For example, if two bundles install the same common bundle it would be installed twice:

public class Feature1Bundle implements GuiceyBundle {
    @Override
    public void initialize(GuiceyBootstrap bootstrap) {
        bootstrap.bundles(new CommonBundle);
    }
}

public class Feature2Bundle implements GuiceyBundle {
    @Override
    public void initialize(GuiceyBootstrap bootstrap) {
        bootstrap.bundles(new CommonBundle);
    }
}

GuiceBundle.buider()
    .bundles(new Feature1Bundle(). new Feature2Bundle())
    ...

To workaround such cases deduplication mechanism* was introduced: instances of the same type are considered duplicate if they are equal.

Equals method

In order to resolve "common bundle/module problem" bundle/module must only properly implement equals method:

public class CommonBundle implements GuiceyBundle {
    ...

    @Override
    public boolean equals(final Object obj) {
        return obj != null && getClass().equals(obj.getClass());
    }
}

Tip

Guicey provide base classes for such cases: UniqueGuiceyBundle for unique bundles and UniqueModule (or UniqueDropwizardAwareModule) for unique guice modules. So bundle above could be simplified to:

public class CommonBundle extends UniqueGuiceyBundle { ... }

Equals logic could be more complicated: for example, it may involve constructor parameters comparison to treat as duplicates only instances with the same parameters.

Unique items

When it is impossible to properly implement equals method (for example, because target bundle or module is 3rd party) you can simply explicitly declare them as unique:

GuiceBundle.builder()
    .uniqueItems(Some3rdPartyBundle.class, 
                 Some3rdPartyModule.class)

Now only one instance of Some3rdPartyBundle and Some3rdPartyModule will be registered and all other instances considered as duplicate.

General unique logic

.uniqueItems() method above is actually a shortcut for custom deduplication mechanism registration (most common case).

But you can implement your own deduplication logic and register with:

GuiceBundle.builder()
    ...
    .duplicateConfigDetector((List<Object> registered, Object newItem) -> {
         if (newItem isntanceof Some3rdPartyBundle) {
             // decide if item collide with provided registered instances (of the same type)
             return detectedDuplicate // instance that registered is duplicate to or null to accept item
         }           
         // allow instance registration
         return null;    
    })

Important

This does not override equals method logic: custom de-duplication mechanism is called only after equals check.

Warning

You can't use .duplicateConfigDetector() and .uniqueItems() at the same time - one would override another (depends on order). In case of override you will only see warning in logs.

Legacy mode

Old guicey "1 instance per class" behaviour could be recovered with bundled detector:

.duplicateConfigDetector(new LegacyModeDuplicatesDetector())

Reporting

Configuration diagnostic report (.printDiagnosticInfo()) shows all registered instances and ignored duplicates.

For example, if we have module declared to be unique by constructor value:

public class VMod extends AbstractModule {

    private int value;

    public VMod(int value) {
        this.value = value;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof VMod && value.equals(obj.value);
    }

}   

If modules are registered like this:

 GuiceBundle.builder()
    .modules(new VMod(1), new VMod(1), new VMod(2), new VMod(2))
    .printDiagnosticInfo()
    .build()

Report would contain:

GUICE MODULES =
        VMod                          (com.mycompany) *REG(2/4) 

APPLICATION
    ├── module     VMod                          (com.mycompany)
    ├── module     -VMod                         (com.mycompany) *DUPLICATE
    ├── module     VMod#2                        (com.mycompany)
    ├── module     -VMod#2                       (com.mycompany) *DUPLICATE

Where you can see that 2 of 4 registered modules of type VMod were registered. Note that instances are numbered (#2) in order of registration (without duplicates) so you can always see what bundle were considered as original (and see registration order when bundles of the same type are registered in different bundles).

Limitations

Guice modules

Transitive guice modules are not counted during de-duplication.

For example,

public class MyModule extends AbstractModule {}

public class SomeModule extends AbstractModule {
    @Override
    public void configure() {   
        // transitive module
        install(new MyModule());
    }
}                           

GuiceBindle.builder()
    .modules(new OtherModule(), new MyModule())
    .uniqueItems(MyModule.class)

This will not work because guicey is not aware of transitive modules (guicey can only know modules tree on class level, but can't see exact instances).

BUT guice natively support de-duplication of equal modules, so if your module have proper equals

public class MyModule extends UniqueModule {}

Then guice will perform de-duplication itself.

Warning

Guice will perform de-deplication itself only if both equals and hashCode properly implemented (like in UniqueModule)

Note

Guice can also de-duplicate bindings: if bindings from different module instances are the same then guice will simply ignore duplicate bindings.

Dropwizard bundles

Guicey can see transitive dropwizard bundles and properly apply de-duplication logic. For example,

public class MyBundle implements ConfiguredBundle {}

public class OtherBundle implements ConfiguredBundle {
    @Override
    public void initialize(Bootstrap bootstrap) {   
        // transitive bundle
        bootstrap.addBundle(new MyBundle());
    }
}                           

GuiceBindle.builder()
    .dropwizardBundles(new OtherBundle(), new MyBundle())
    .uniqueItems(MyBundle.class)

This will work because guicey use special proxy to intercept transitive registrations (so, essentially, transitive registrations are treated the same as direct)

Note

This means that if you have "common dropwizard bundle" problem, then you can simply register it with guicey and it will be able to properly de-duplicate it.

BUT guicey does not "see" directly installed bundles (intentionally!). For example,

bootstrap.addBundle(new MyBundle())
bootstrap.addBundle(GuiceBindle.builder()
                .dropwizardBundles(new OtherBundle())
                .uniqueItems(MyBundle.class)
                .build())

This will not work because guicey see only directly registered bundles: OtherBundle and transitive MyBundle, and so MyBundle would be registered twice.