Search code examples
springdependency-injectionguicejsr330

When should I use binding annotations vs more-specific interfaces?


Question

What criteria should be used when deciding between:

  • specifying a dependency with an annotation, and
  • specifying a dependency with a more specific interface

Example

Suppose I have:

interface FooLoader {
    Foo loadById(long id);
}

class DBFooLoader implements FooLoader {
    ... jdbc etc. etc. ...
}

class CachingFooLoader implements FooLoader {
    ...
    @Inject
    public CachingFooLoader(FooLoader delegate) {
        this.delegate = delegate;
    }
    ...
}

Suppose I want to bind FooLoader to CachingFooLoader, I have [at least] two ways to wire this:

Use an annotation binding

Change:

public CachingFooLoader(FooLoader delegate)

to:

public CachingFooLoader(@NonCaching FooLoader delegate)

and then:

bind(FooLoader.class).annotatedWith(NonCaching.class).to(DBFooLoader.class);

Create a more specific interface

Change:

public CachingFooLoader(FooLoader delegate)

to:

public CachingFooLoader(NonCachingFooLoader delegate)

where NonCachingFooLoader simply extends FooLoader, and then have DBFooLoader implement NonCachingFooLoader, and wire up accordingly.

My thoughts

I am drawn to using an annotation binding for multiple reasons:

  • Keys can be more easily reused than interfaces, which decreases the combinatorial explosion that interfaces would suffer from.
  • It is less invasive: configuration stays in Guice modules, rather than "poisoning" classes.

However, creating a more specific interface has its advantages too:

  • Interfaces have more meaning. Typically only Guice will read the annotation, where as interfaces are used for much more.

So, what criteria should be used to determine which approach to take?

(Spring users, as far as I can tell, this is what you guys call qualifiers.)


Solution

  • Use specific interfaces only if it makes sense, i.e. they have to offer a different API and thus other classes will use them in a specific way.

    If they offer the same "service" in different ways, then use only one common interface and differentiate implementations with annotations.