My first impression with Guice has been somewhat mixed. It is really refreshing to work with the extremly well-designed api's, packed with the power of generics and annotations from Java 5. Fluent interfaces like
binder.bind(Service.class).annotatedWith(Blue.class).to(BlueService.class)
make ideal DSLs in Java and give you the feeling that you are programming to Guice rather than to Java. This is a similar feeling that you get when programming to Rails (as opposed to programming in Ruby). However, I came across some stumbling blocks which have been major irritants for the problem that I was trying to solve. Just to point out that I have only fiddled around with Guice for a couple of days, and may have missed out lots of details which can offer better solutions to the problems. Any advice, suggestions from the experts will be of great help. Here are some of the rants from my exercise with porting an existing application into Guice :
A Different way to look at Configuration
Many people have blogged about the onerous XML hell of Spring and how Guice gets rid of these annoyances. I think the main difference between Guice and Spring lies in the philosophy of how they both look at dependencies and configuration. Spring preaches the non-invasive approach (my favorite) and takes a completely externalized view towards object dependencies. In Spring, you can either wire up dependencies using XML or Spring JavaConfig or Groovy-Spring DSL or some other option like using Spring-annotations. But irrespective of the techniques you use, dependencies are always externalized :
@Configuration
public class MyConfig {
@Bean
public Person rod() {
return new Person("Rod Johnson");
}
@Bean(scope = Scope.PROTOTYPE)
public Book book() {
Book book = new Book("Expert One-on-One J2EE Design and Development");
book.setAuthor(rod()); // rod() method is actually a bean reference !
return book;
}
}
The above is an example from Rod Johnson's blog post - the class
MyConfig
is an externalized rendition of bean configurations. It uses Java 5 annotations to define beans and their scopes, but, at the end of the day, all it does is equivalent to spitting out the following XML :<bean id="rod" class="Person" scope="singleton">
<constructor-arg>Rod Johnson</constructor-arg>
</bean>
<bean id="book" class="Book" scope="prototype">
<constructor-arg>Expert One-on-One J2EE Design and Development</constructor-arg>
<property name="author" ref="rod"/>
</bean>
Guice, on the other hand, treats configuration as a first class citizen of your application model and allows them right into your domain model code. Guice modules indicate what to inject, while annotations indicate where to inject. You annotate the class itself with the injections (through @Inject annotation). The drawback (if you consider it to be one) is that you have to
import com.google.inject.*
within your domain model. But it ensures locality of intentions, explicit semantics of insitu injections through metadata programming.// what to inject : a sample Module
public class TradeModule extends AbstractModule {
protected void configure() {
bind(Trade.class).to(TradeImpl.class);
bind(Balance.class).to(BalanceImpl.class);
bindConstant().annotatedWith(Bond.class).to("fixed income");
bindConstant().annotatedWith(I.class).to(5);
}
}
// where to inject : a sample domain class
public class TradingSystem {
@Inject Trade trade;
@Inject Balance balance;
@Inject @Bond String tradeType;
int settlementDays;
@Inject
void setSettlementDays(@I int settlementDays) {
this.settlementDays = settlementDays;
}
}
Personally I would like to have configurations separated from my domain code - Guice looked to be quite intrusive to me in this respect. Using Spring with XML based configuration allows a clean separation of configuration from your application codebase. If you do not like XML based configurations, use Spring JavaConfig, which restricts annotations to configuration classes only. Cool stuff.
Annotations! Annotations!
Guice is based on Java 5 annotations. As I mentioned above, where-to-inject is specified using annotations only. The plus with this approach is that the intention is explicit and locally specified, which leads to good maintenability of code. However, in some cases, people may jump into overdose of annotations. Custom annotations should be restricted to minimum and should be used *only* as the last resort. Guice provides a Provider<T> abstraction to deal with fine grained instantiation controls. In fact Provider<T> is an exceptionally simple abstraction, but can be used very meaningfully to implement lazy variants of many design patterns like Factory and Strategy. In my application I have used Provider<T> successfully in implementing a Strategy, which I initially implemented using custom annotations. Lots of custom annotations is a design smell - try refactoring your design using abstractions like Provider<T> to minimize them.
Problems with Provider<T>
However, I hit upon a roadblock while implementing some complex strategies using Provider<T>. In many cases, my Strategy needed access to contextual information in order to decide upon the exact concrete strategy to be instantiated. In the following example, I need different strategy instances of
CalculationStrategy
depending on the trade type.interface CalculationStrategy {
void calculate();
}
public class TradeValueCalculation {
private CalculationStrategy strategy;
private Trade trade;
// need different instances of strategy depending on trade type
public TradeValueCalculation(Trade trade, CalculationStrategy strategy) {
this.trade = trade;
this.strategy = strategy;
}
public void calculate() {
strategy.calculate();
}
}
I cannot use any custom annotation on constructor argument strategy, since I need the polymorphic behavior for different instances of the same class. I tried with the Provider<T> approach :
public class TradeValueCalculation {
private Provider<CalculationStrategy> strategy;
private Trade trade;
@Inject
public TradeValueCalculation(Trade trade, Provider<CalculationStrategy> strategy) {
this.trade = trade;
this.strategy = strategy;
}
public void calculate() {
strategy.get().calculate();
}
}
Still the Provider does not have the context information .. :-( and my problem is how to pass this information to the Provider. Any help will be appreciated ..
On the contrary, solving this in Spring is rather simple by declaring multiple bean configurations for the same class. And this works like a charm in the XML as well as the Java variant of configuring Spring beans.
Some other pain points ..
- Guice user guide recommends using Provider<T> to inject into third party classes. This looked quite obtuse to me since it goes against the philosophy of less code that Guice preaches. Spring provides much elegant solutions to this problem because of its non-invasiveness property. Guice, by virtue of being an intrusive framework, had to provide this extra level of indirection to DI into classes for which I do not have the source code.
- One specific irritant in Guice is the literal injection, which forced me to use a custom annotation everytime I wanted to inject a String literal.
- Another feature which would have been very useful for me is the ability to override bindings through Module hierarchies. In one of my big applications, I have multiple components where I thought I can organize my Guice Modules in a similar hierarchy with specific bindings being overridden in specific modules. This is definitely the DRY approach towards binding implementations to interfaces. Guice did not allow me to do this - later I found the similar topic discussed in the development mailing list, where a patch is available for overriding bindings. I am yet to try it out though ..
It will be extremely helpful if some of the Guice experts address these issues and suggest workarounds. I like the terseness of the framework, the api's are indeed very intuitive and so are the error messages. The Javadocs are extremly informative, though we need more exhaustive documentation on best practices of Guice. Guice is really lightweight and is published to be very fast (I am yet to test on those benchmarks with Spring though). I hope the Google guys look into some of the pain points that early adopters have been facing with Guice ..
7 comments:
Could you not do
bind(String.class).to("some other string) for string literal injection?
If you want to bind constants without an explosion of annotations, consider using @Named. Also keep in mind that whenever an annotation has parameters, you can bind separately to each different possible *value* of the annotation, then guice will only fall back on the binding to the annotation *type* if it didn't find a specific value match. (This is precisely how @Named works as well.)
Let's see. Your problem with your Provider sounds like issue 27,
http://code.google.com/p/google-guice/issues/detail?id=27&can=2&q=
it's tagged for our 1.1 release.
Injecting classes that don't conform strictly to our @Injecty expectations should be enabled by provider interception, issue 78, also high-priority for 1.1.
Overriding bindings is filed as issue 80 and we are thinking carefully about it.
I hope this is helpful.
[for kevin]:
Thanks a lot for the pointers. I will keep an eye on future releases, which, I am sure will solve a lot of the pain points. I find a lot of good points in Guice, the api's are very intuitive and easy to use.
There's quite a bit of overlap between Guice and Tapestry 5 IoC and the advantages and disadvantages to both sides. Where T5 IOC differentiates is that the module is responsible for building the services, not just describing how injections occur. Each service gets its own "service builder method" and dependencies are passed to the method via the method parameters; in turn, it can instantiate and configure the service implementation and return it.
Sometimes it does more, such as registerring the service for event notifications ... sometimes there is no service implementation, just another service that creates the implementation on the fly (the magic of Javassist, properly tamed).
In any scenario, the service implementation is blind to the fact that it is built by T5 IOC.
Guice is better than XML (HiveMind, Spring, etc.) but still makes the mistake of trying to own object creation/instantiation instead of delegating to Java code, which is the best language for describing how to instantiate and configure Java objects.
[for Howard:]
[Guice is better than XML (HiveMind, Spring, etc.) but still makes the mistake of trying to own object creation/instantiation instead of delegating to Java code, which is the best language for describing how to instantiate and configure Java objects.]
Could not get the above point. Instantiation of the implementation is the responsibility of the container and Guice does that, and so does Spring as well. Am I missing anything here ?
Howard, custom providers enable you to delegate to Java code for instantiation. It's nice to be able to do this, but there's no point in always writing such code by hand. Guice's TypeLiteral also takes some of the pain out of the framework creating objects for you.
[for bob]:
My understanding is that custom providers are the last resort, when I would like to have more fine grained control over instantiation of objects e.g. implementing custom strategy pattern. Please confirm if this is the recommended practice. Of course it will help having more flexibility in custom providers to pass context information (as I have blogged in the post). Kevin has also identified it as one of the issues to be addressed for a future release of Guice.
Post a Comment