google / google/guice

Allow Binding annotations to be used as dependants in providers

Open
#1,098 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
12.7k
Forks
1.7k
Avg merge
11m
Merged PRs (30d)
2

Description

> As a guice-user looking to use interfaces wherever possible I would like a convenient way to specify more specific types than my constructor (or field) signature allows for, so that I might more easily express my dependencies.

Hey guys,

so we have a use case that I don't believe is really uncommon. The gist is that we have a factory that has a dependency on a bunch of providers. It exposes a single method that switches based on its input and the state of the application to select one of these providers to use. The values given by these providers could be specific, but they would rather use an an interface that is the interface on this factory method.

some code, given that I have:

```java
public class DomainKnowledgeSwitchingFactory{
//...

@Inject
DomainKnowledgeSwitchingFactory(
Provider scenarioOneHandlerProvider,
Provider scenarioTwoHandlerProvider,
OtherRuntimeDeps otherRuntimeDepsPossiblyAssistedInjectProvided
) //{ ... }

public DomainInterfaceType makeDomainInterfaceTypeForCurrentSItuation(CurrentSituation situation){
if(complexDomainLogicThatDerrivesScenarioOne(situation)){
return scenarioOneHandlerProvider.get()
}
else if (complexDomainLogicForScenarioTwo(situation)){
return scenarioTwoHandlerProvider.get()
}
else { throwOrExecuteSomeDefault() }
}
}
```

I think it should be pretty apparent that _I don't want `scenarioOneHandlerProvider` to provide the same type as `scenarioTwoHandlerProvider`_. So, given that I want to return different types for scenario one and scenario two, these are my options:

## existing option one ##

request the most specific type at provider-injection:

```diff
- Provider scenarioOneHandlerProvider,
+ Provider scenarioOneHandlerProvider,
- Provider scenarioTwoHandlerProvider,
+ Provider scenarioTwoHandlerProvider,
```

downside:

Testing this is harder than it needs to be. Nothing about this code has a runtime dependency on `SOneHandlerImplementsDomainInterface`, yet in testing I'm now going to have to create a hard instance of that type, even though this code compiles _and expresses its intent through variable names_ with the previous type. Yes I'm aware of mocking frameworks, but creating an instance of a concrete type is _always_ going to be harder than creating an instance of the interface for testing!

## existing option two ##

use binding annotations:

```diff
- Provider scenarioOneHandlerProvider,
+ @Named("ScenarioOne") Provider scenarioOneHandlerProvider,
- Provider scenarioTwoHandlerProvider,
+ @Named("ScenaroTwo") Provider scenarioTwoHandlerProvider,
```

then update your module bindings:

```java
bind(DomainInterfaceType.class).annotatedWith(Names.named("ScenarioOne")).to(SOneHandlerImplementsDomainInterface.class)
bind(DomainInterfaceType.class).annotatedWith(Names.named("ScenarioTwo")).to(STwoHandlerImplementsDomainInterface.class)
```

downside:

In my production code I'm actually injecting 5 such providers and soon there will be a 6th. This means the calling module(s) will each need 5 separate bindings. With one growing each time. To a user accustomed to spring or other explcit-configuration dependency injection systems this might be OK, but to those of us who work hard to keep things auto-wired nicely, its a pain in the butt.

## proposed new option ##

increase flexibility of providers: I was hoping to write something like

```diff
- Provider scenarioOneHandlerProvider,
+ @ActualType(SOneHandlerImplementsDomainInterface.class) Provider scenarioOneHandlerProvider,
- Provider scenarioTwoHandlerProvider,
+ @ActualType(SOneHandlerImplementsDomainInterface.class) Provider scenarioTwoHandlerProvider,
```

Now this could be added as another special case pre-supplied `BindingAnnotation`, or it might (more generally) register annotation instances as available to providers, while also lifting provides from implementation providers to type-alias providers:

```java
bind(DomainInterfaceType.class)
.annotatedWith(ActualType.class)
.to((ActualType annotationInstance) -> { annotationInstance.value })
```

or perhalps with a similar provider method:

```java
@ProvidesAlias DomainInterfaceType makeDomainInterfaceType(ActualType actualType) {
return actualType.value
}
```

---

many thanks for a great library and keep up the good work!

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.