Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Tuesday, April 03, 2007

Tapestry 5 IoC from a Guice Perspective

So Guice has a few good ideas, and with Tapestry 5 still in a fluid state, I've absorbed a few of them into Tapestry 5 IoC. At the core, is the key ObjectProvider interface:

public interface ObjectProvider
{
    <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ServiceLocator locator);
}

Implementations of this interface plug into a chain-of-command within Tapestry and, via the AnnotationProvider, can query for the presence of annotations to help decide exactly what is to be injected. A lot of Guice's examples, things like the @Blue service, are easily replicated in Tapestry 5 IoC, there's just not a lot of need for it. I supposed it would look something like:

public class BlueObjectProvider {
  private final Map<Class,Object> _blueObjects;

  public BlueObjectProvider(...) { ... }

    <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ServiceLocator locator) {
      if (annotationProvider.getAnnotation(Blue.class) == null) return null;

      return objectType.cast(_blueObjects.get(objectType));
  }
}

Implicit here is a service configuration of implementations, keyed on object type, used when the @Blue annotation is present. Here's what the module would look like:

public class BlueModule {

  public ObjectProvider buildBlueProvider(Map<Class, Object> configuration)
  {
    return new BlueObjectProvider(configuration);
  }

  public void contributeMasterObjectProvider(
    @InjectService("BlueProvider") ObjectProvider blueProvider,
    OrderedConfiguration<ObjectProvider> configuration) {
    
    configuration.add("Blue", blueProvider);
  }

  public void contributeBlueProvider(MappedConfiguration<Class, Object> configuration,
    @InjectService("MyBlueService") Service myBlueService) {
    configuration.add(Service.class, myBlueService);
  }
}

public class OtherModule {

  public OtherService build(@Inject @Blue Service service) {
    return new OtherServiceImpl(service);
  }
}

On the one hand, this is more verbose, since its somewhat more procedurally based than Guice's approach, which is pattern based. On the other hand, it demonstrates a couple of key features of Tapestry 5 IoC:

  • Service construction occurs inside service builder methods; there's no layer of abstraction around service instantiation, configuration and initialization ... you just do it in Java code. There simply isn't a better language for descibing instantiating Java objects and invoking Java methods than Java itself.
  • Dependency injection is a concern of the module not the service implementation. The special annotations go in the module class, and the service implementation class is blissfully unaware of where those dependencies come from or are identified and obtained.
  • Dependencies are passed to the service builder methods, which do whatever is appropriate with those dependencies; in many cases, the dependencies are not passed to the instantiated class, but are used for other concerns, such as registering the new service for event notifications from some other service.
  • The contributeBlueProvider() method can appear in many different modules, and the results are cumulative. This allows one module to "lay the groundwork" and for other modules to "fill in the details". And, again, if this style of injection ("flavored" with the annotations) really became popular, it would be easy to integrate it into the central Tapestry 5 IoC module so it could be leveraged everywhere else.

In fact, in the OtherModule class, you can see how the injection lays out once the groundwork is prepared: Just an @Inject qualified with @Blue and you're done. Again, I can't emphasize enough how well moving construction concerns into the service builder methods works; it gives you complete control, it's unit testable in a couple of different ways, it keeps the service implementations themselves clean (of dependencies on the IoC container -- even in the form of annotations). It also keeps explicit what order operations occur in. HiveMind chased this with ever more complex XML markup to describe how to instantiate, configure, and initialize services.

That last feature, the interaction of multiple modules discovered at runtime, is the key distinguishing feature, and the one that's hardest to grasp. For me, it is the point where an IoC container transitions from an application container to a framework container. In an application container, you explicitly know what modules are present and how they will interact. It's your cake, starting from raw flour, sugar and eggs. Nothings in the mix that you didn't add yourself.

With HiveMind and Tapestry 5 IoC, the goal is to build off of an existing framework, and its interwoven set of services. The framework, or layers of framework, provide the cake, and you are trying to add just the icing. Configurations are the way to inject that icing without disturbing the whole cake, or even knowing the cake's recipe.

So Guice is a good kick in the pants, and Tapestry 5 IoC has evolved; the old @Inject annotation, which used to include a string attribute to name the thing to inject, has been simplified. The @Inject annotation is still there, but the string part is gone. Tapestry primarily works from the object type to match to the lone service implementing that interface. When that is insufficient, there's the MasterObjectProvider and Alias service configurations, which provide plenty of room to disambiguate (possibly by adding additional annotations to the field being injected).

The big advantage is type safety; refactor the name of a service interface and you'll see little or no disruption of the service network, because dependencies are almost always expressed in terms of just the (refactored) service interface, rather than any additional layer of "logical name".

I think there's a growing issue with Google-worship. It was interesting and disturbing to see so many people announce "Spring is dead! Long live Guice!" on various blogs (and I'll even admit to a little envy ... how come Tapestry 5 IoC doesn't generate this kind of interest?). Guice itself is pretty useless by itself, just as Spring's IoC container is, by itself, useless. The value of Spring is in what's built on top of the container: Wrappers for JDBC, Hibernate, JPA, JMS and every other acronym you can name. Transaction support and interceptors. And, more recently, the really solid AspectJ layer. If Guice wants to be a Spring-killer, or even a real player in the IoC space, it needs to embrace and extend Spring: make it easier to use those Spring integrations than it would be using Spring natively.

23 comments:

Jacob Hookom said...

With the CoR solution proposed, what about allowing a provider to assert the value is :null: without allowing propagation?

Howard said...

This hasn't been an issue; I really prefer the "don't allow null, don't return null" approach, wherever possible. It's very easy to create a no-op implementation of a service (HiveMind can do it automatically, and that would be easy to port to T5).

ThiagoHP said...


how come Tapestry 5 IoC doesn't generate this kind of interest?


Maybe because it is not advertised as a standalone library. Interestringly, I was just about to post a message in tapestry-users asking whether T5 IoC can be used outside of T5. Can it?
If yes, why don't you give it another name, making it clear that it is not just a part of T5?

Howard said...

Because in T4, I constantly got bashed for NOT using Spring, even though Spring doesn't have the key feature, the configurations, that I need. So I thought I'd take a low profile on the IoC container in T5 ... which is why its amusing that Guice can fully embrace Not Invented Here and get accolades for it (from a blogging minority).

ThiagoHP said...

It would be very, very interesting if there was a short, simple T5-IoC tutorial just like HiveMind's one. ;) What do you think about it? I'm curious to feel the difference between using Guice and T5-IoC and Spring to write the same application. I've just used Spring yet, but I'll play with Guice and T5-IoC as soon as I can. :)

xvik said...

This would be little offtopic, but im always want to know.. why you choose dojo for ajax and not google web toolkit?

Bob said...

This looks like Spring's JavaConfig, not Guice, and I think I prefer Spring's JavaConfig over Tapestry IoC. I don't think you fully appreciate how much extra code you're asking users to write and how obscure that code is. Then again, maybe we just use DI more pervasively.

If you want to know why Guice is so popular, the best advice I can give is, try it. Who knows? You might decide to lobby for a couple new features and adopt it, in which case you could spend more time on your web framework.

I'm not building a consulting practice around Guice. I don't want Guice to be a "Spring-killer," and it doesn't matter to me if you consider it a "real player in the IoC space." All that matters is that Guice enables me to write code how I want to write code. If that's not how you want to write code, there's always Spring/Tapestry, but I'm confident users will appreciate the higher level of maintainability Guice provides in the long run.

Howard said...

It's a bit different from Spring Config, in that it understands that there are many modules out there.

A lot of framework design is knowing what areas users will accept "magic" and what areas they won't.

The arbitrary example I provided is not based on how T5 is normally used, but was twisted to around the Guice example. However, it could easily be streamlined into a reusable standalone module that would hide the details.

So ... in Guice, say I have a dependency that is an event source. When I create my service implementation (which implements the listener interface), how do I register my new service with the event source service?

I suspect in Guice you would have to inject the event source as a dependency, then have code in the new service to perform the registration.

There's a bunch of other areas where this model works very well, such as the cases where there is no service implementation class at build time, but it is generated at runtime. I do this a lot for common Gang-of-Four design patterns, such as chain of command.

So, what would a distributed contribution look like in Guice?

Bob said...

Are you asking me to address your use case, or do you want to know about distributed configurations in general? If you want me to address the use case, can you please give a little more detail?

Sony Mathew said...

Howard, you debated me on stage at my BOF at TSSJS "against" all the arguments you have made here. I am not really suprised - i expected folks to come around eventually. Ofcourse annotations are the catalyst now - but i made these arguments before annotations and demostrated how it can be done in pure Java as a Design pattern along wilth all its advantages (Google Context IoC).

Some of your (my) arguments:

1) There simply isn't a better language for descibing and instantiating Java objects and invoking Java methods than Java itself !!! Absolutely Ludicrous to think otherwise. You argued intensly for XML. Your words: HiveMind chased this with ever more complex XML markup to describe how to instantiate, configure, and initialize services.

2) The big advantage is type-safety. Additionally, with Context IoC Design Pattern there are NO string reference identifiers. You argued only unit-testing is important. I beileve in unit-testing - but desire the benefits of type-safety.

3) The value of Spring is in what's built on top of the container - yes !!!. Give me the Transaction & Security Decorators !!! Let me do IoC in Java using the IoC design pattern (Google Context IoC). I can decorate the services in my Application/ModuleContexts myself - its NOT complicated and less error prone. I plan on open-sourcing a util of Decorators eventually.

As far as Annotations over XML - i'm still mixed and still trying to evaluate it. Definitely has some convenience.

With the Context IoC Design Pattern you have:
public class MyServiceImpl implements MyService {
public interface Context {
XyzService getBlue();
XyzService getRed();
}
}

With Annotations you have:
public class MyServiceImpl implements MyService {
@Inject
public void setBlue(@Blue XyzService blue);

@Inject
public void setRed(@Red XyzService red);
}

Not much different in expressiveness - both are compile checked in Java. With Context IoC Design Pattern - There isn't a container doing runtime "injections" based on Annotations there is just an implementation of your Contexts (which can be delegated to a Container if so chosen!). Lightning FAST!!! Added benefit ofcourse you can refactor your Contexts (and their implementations) - generalize, decompose - its Java OO !!! Create Modules of Contexts and tie them all up in a Root Context. You'll see all kinds of patterns emerging. Annotations do have a higher degree of convenience however.

By the way, i intro'd Tapesty at my company a while back (it was superior to JSF). I chose NOT to use the Hivemind IoC container (good thing, its being replaced). The real usefulness of Tapestry is in the ease with which HTML can be componentized - I wish you guys would focus on improving that intead of Hivemind and IoC Containers and keep all that under the hood.

Bob said...

"There simply isn't a better language for descibing instantiating Java objects and invoking Java methods than Java itself."

With Guice, most of the time, you don't manually write injection code at all. In cases where you do need to instantiate objects or invoke methods manually, you implement Provider<T> and write code in Java.

There may not be a better language, but it is even better to not write the code at all if you don't have to.

Howard said...

See, I used to have that position, that you should let the framework figure out how to autowire and configure your objects from plain cloth. But I grew out of it, since it always sets limits on what you can do (for instance, my event registration example, as well as the created-on-the-fly example). And the separation of concerns (service creation vs. service operation) is very valuable.

Still, it would be an interesting challenge to support an alternative approach to "binding" interfaces to implementation classes, and allowing the injection annotations to occur on the constructor.

A big downside to your approach is when you are trying to apply IoC techniques to classes that you don't have control over, and so can't add the necessary annotations.

Bob said...

That's why Guice supports both approaches: automatic wiring and explicit wiring. We understand that you sometimes need to manually wire an object, but that doesn't mean you should always write explicit wiring code. "A foolish consistency is the hobgoblin of little minds."

Sony Mathew said...

Bob, either you instantiate an object outright (with new) or instantiate via reflection using meta-data as you do from Annotations or Spring from XML. There isn't a "no code" option. The former is clearly superior assumming that construction code is NOT repeated more than once for a given class type (or atleast once per constructor overload for a given usecase).

Lets compare (for binding the IoC dependencies depcited in code sample from my previous comment):

Guice way:
public class MyModule implements Module {
public void configure(Binder binder) {
bind(XyzService.class)
.annotatedWith(Blue.class)
.to(BlueXyzService.class);

bind(XyzService.class)
.annotatedWith(Red.class)
.to(RedXyzService.class);
}
}


Context IoC Design Pattern Way

//Module just implements the Contexts of the objects with dependencies.
//Here Your IDE will force you to implement all dependencies of MyServiceImpl right away.

public class MyModule implements MyServiceImpl.Context {
private XyzService blue;
private XyzService red;

public MyModule() {
blue = new BlueXyzService();
red = new RedXyzService();
}

public XyzService getBlue() {
return blue;
}

public XyzService getRed() {
return red;
}
}

I can show how decorations for Transactions etc. can be done if you'r interested. Howard's approach essentially replaces my getXXX() methods with buildXXX() methods with additional annotations on constructor params (not sure why, assuming for some separation of concerns, instead of just accessing them directly).

Bob said...

Sony, how would you inject dependencies into RedXyzService and BlueXyzService?

Sony Mathew said...

Bob, how would you Inject dependencies into BlueXyzService and RedXyzService?
Using the same design pattern in a recursive way:

First: I forgot to mention in previous comment:
Each Impl takes an instance of their Impl.Context in their constructor.

//Implemention of BlueXyzService - where it declares its dependencies.

public class BlueXyzService implements XyzService {
public interface Context
public XyzStore getBlueStore();
}

public BlueXyzService(Context cxt) {
this.context = cxt;
}
}

//Implemention of RedXyzService - where it declares its dependencies.

public class RedXyzService implements XyzService {
public interface Context
public XyzStore getRedStore();
}

public RedXyzService(Context cxt) {
this.context = cxt;
}
}

//Adding to the same Binding Module I showed in previous comment.

public class MyModule implements
MyServiceImpl.Context,
BlueXyzService.Context,
RedXyzService.Context
{
private XyzService blue;
private XyzService red;
private XyzStore blueStore;
private XyzStore redStore;

public MyModule() {
blue = new BlueXyzService(this);
red = new RedXyzService(this);
blueStore = new BlueXyzStore(this);
redStore = new RedXyzStore(this);
}

public XyzService getBlue() {
return blue;
}

public XyzService getRed() {
return red;
}

public XyzStore getBlueStore() {
return blueStore;
}

public XyzStore getRedStore() {
return redStore;
}
}

Generally a collection of related usecases works best for a Module.
Then aggregate several modules into a RootApp via composition.
I would also have each Module take the RootApp in its constructor so
it can navigate to other modules etc. if it needs something.
But anything is possible - its upto your Java/OO design.

Decoraters for each instance is demonstrated (if the Decorators exist).

blue = new BlueXyzService();
blue = (XyzService) transactionMgr.decorate(blue, TransactionOptions.REQUIRES);
blue = (XyzService) securityMgr.decorate(blue, "adminRole");
etc...

Jesse Kuhnert said...

sony mathew: I think by that point I'd rather have it done by "magic" again.

Now I can't tell if my classes are implementing a system concerned design pattern or a context/ioc specific interface/design pattern... =p

Sony Mathew said...

Jesse, you are welcome to magic.

However, Its not magic, just hand-waving and gesturing to distract the audience. Its just an indirect way to construct and wire using reflection after reading piles of meta-data via XML or Annotations.

The new fad now is byte-code generation of class wrappers which in Tapestry/Javassist/OGNL case took up over 200 Megs of memory for a 100 concurrent user load test against our application (not counting Page Templates whose cache was disabled for this test).

Our own object graph took up less than 1/3 of Tapestry's space. Our memory footprint was easily examined since our object graphs always begin from our Context Modules and easily navigable from there.

You want magic? - With Context IoC Design Pattern - update a dependency and your IDE tell will you immediately all the modules that need to implement that dependency - NOW that is magic of real value. Not to mention the refactoring benefits. Also - I personally like the neatness of each object's dependencies being organized into its own Context - which is implemented by one or more modules directly and provides static compile time safety.

Static compile checks are your friend - not to mention helps your IDE be your best-friend. Some will argue that only unit tests are important. Maybe in fantasy Open-Source world but in real IT shops deadlines always win and UnitTests always lag behind. Additionally, in my experience, only seasoned senior developers write comprehensive unit-tests covering "ALL" important scenarios - less seasoned folks always miss many scenarios that are not caught until much later.

p.s sorry Howard for taking over ur Blog.

Jesse Kuhnert said...

Sony:

Sounds like you might have been doing something funky in your Tapestry app. I profiled a few small apps via yourkit recently and found that after gc running my memory footprint stayed at around 22 megs.

Of course this was using compiled OGNL statements so the temporary object graph you'd normally get doesn't exist anymore.

Good luck with your context pattern, I've personally found that the best kinds of context implementations are the kind where you don't know they exist.

Howard said...

Sonny,

I don't have a perfect memory from TheServerSide, but my recollection was that I was willing to pay for some pain in terms of refactoring and the use of XML to gain the benefits of a true container, in terms of divorcing myself from concerns about service instantiation, configuration and cleanup. You can think of approaches such as Tapestry 5 IoC and Guice as "have my cake and eat it to." What's interesting is that over the last couple of years I've learned a bit more about the Java Memory Model and thread concurrency issue, which makes me even happier to shunt those issues out of my main code and let the container take care of proper thread safe, just in-time instantiation.

Your statically defined service network is fine, I'm sure, for you contained application ... but as a Tapestry user, you must be aware of the myriad extension points offered by Tapestry. HiveMind was created as a plugin or framework IoC container (for Tapestry) and that nuanced difference is something I've struggled to explain to quite a number of people. When the service network is described only in code, it is locked together in a way that doesn't allow any selective overriding.

Sony Mathew said...

Jesse,

We constrained our JVM Memory and ran the load test. The Heap analysis after JVM OutOfMemory crashes always showed over 200 Megs of Tapestry/Javassist/OGNL, bulk of which looked like Javassist generated Class defs within a Hivemodule instance. We are still on Tapestry 4.0.x I believe, things may have been improved since then. Not really a problem - just showed our app needs more wiggle room.

"Best kinds of context implementations are the kind where you don't know they exist".
Not sure that is valid since you have to know a lot whether you wireup using XML, Annotations, or Java. If you mean separation of concerns i.e. your control/service objects are separated from their wire-up - this is true in the case of XML as well as Java. The Context Modules go in a different package (or jar if you prefer).

Sony Mathew said...

Howard,

Concerning myriad extention points, You should consider a more Domain-Specific-XML (DSL) and Well-defined Java interface based plug-in extention points along with good module loading functionality (e.g. OSGI based).

Exposing your entire Tapestry hivemind IoC configuration with its complex domain semantics was not an easy extention mechanism - and i discouraged its use here unless absolutely neccessary. Additionally, Static wiring will not prevent any of the above.

Concerning thread safe, just in-time instantiation and cleanup. Yes, you have me there, we do have to do some of this ourselves. However, most of it is needed regardles of an IoC Container and if you use the simple concepts that a language like Java is built for and provides - things are not as hard as they seem.

For. e.g. your App's object graph is really for one purpose: to service a user, so the scope of our Root Context Module instance is a user's session and so as Java intended static members are auto-scoped to application level, member variables are auto-scoped to session, and method parameters and local variables are scoped to request. This would be true for single-user desktop apps as well.

The issue of thread-safety is minimal being session scoped, only if a user multi-browses and there the implications reach much further than mere wire-up instantion but into state management within the object-graph itself and synchronization needs to handled everywhere state gathers. As a rule, we keep objects always light when instantiated and gathers state only when business rules require it and we synchronize then as necessary, as Java intended. An IoC Container will NOT be able to provide this required deep level of thread synchronization.

Concerning cleanup, Decorators (essentially Dynamic Proxies) can cleanup after invokations. Once written and unit-tested they are forever useful. I need to open-source a utility of these Decorators - so they can be used by everyone. Spring's usefulness lies in their pre-canned Decorators.

Sony said...

Update: Memory Leak with our App was due to Tapestry caching being disabled - so as to profile our code's memory usage during load without our various frameworks' caches consuming any memory. This had the opposite effect - Hivemind/JavaAssist/OGNL go nuts caching new class defs, expressions etc. each time a page template is re-loaded. When caching is enabled - the memory leaks disappear. Not exactly the behavior one expects - but hopefully corrects any memory leak accusations.