Tapestry IOC for Dummies

Charles Roth, 26 January 2012

I. Introduction
The terms "Inversion of Control" (IOC) and "Dependency Injection" (DI) are bandied about a lot these days.  They're supposed to make our life easier, our projects more stable, our classes more modular, and might even cure the common code, er, cold.

But at first glance in actual code, IOC & DI can appear pretty "magical".  They seem to suddenly make "injected" objects appear by magic, and it's too easy to just start accepting that magic without really understanding what's going on.  And like the tale of the Sorcerer's Apprentice, working with spells blindly can have really painful consequences!

This article is the basis for a "brown bag" talk at the ProQuest Ann Arbor office.  Because there are many folks here who do not (yet?) work with Tapestry or IOC, and because there is a wide variety of Tapestry IOC knowledge even amongst the folks working on OneSearch, I'll start with some history, and work forwards to what we currently do with Tapestry IOC.

(Note: while this article only talks about Java, IOC exists for other languages as well.)

II. Why IOC?  Why DI?
For me, it all starts with unit-testing.  Unit-testing and modularity (or "loose coupling", if you prefer) are the wonderful chicken-and-egg of modern object-oriented programming.  Modular objects are easier to test.  Objects that were built so that they can be tested, turn out to be more modular, less coupled, and easier to work with.  Writing code without tests is writing "instant legacy code": just add sweat, and you'll get blood & tears.

The slippery slope of 'new'
The biggest problem with writing testable code is dependencies.  In the old days, if I wrote a class that needed ("depended on") another object, I'd just construct it in-line with "new".  For small, fast objects that just do simple calculation or storage, who cares?  I don't hesitate to write new StringBuffer(), after all. 

But the higher my 'new' object is in the food chain, the more coupled my class gets with that object.  ("Coupled" is such a pristine, euphemistic word.  Dirty, sticky, gluey mess says it better.)  If the 'new' object is really a service that talks to the outside world, then I'm really screwed: now I can't possibly write a test for my class.

Enter IOC and DI
"Inversion of Control" is a pretty vague-sounding buzz word.  In this context, it just means giving up the control of creating 'new' objects myself.  Instead, I politely invite the IOC "container" to do it for me.  The typical implementation of this is called "dependency injection" -- the objects are stuck (as if with a needle) into my class.

But let's start with a concrete example that we can build upon...
 

III. Example -- the "DeDOPEr"
Here's a hypothetical example scenario.  Let's say the U.S. Congress passes a new bill: the "Destroy Online Piracy forEver" act, or DOPE.  It requires that U.S.-based DNS servers change the domain name resolution for websites alleged to contain, or link to, pirated material.

Now it turns out that my favorite, legal, information-sharing site, wackapedia.org, might be at risk of being falsely accused and blacked-out.  So I write a little java class that checks the DNS resolution of wackapedia.org:

   public class DeDoper {
      public boolean wackapediaOkToday() {
         DnsResolver resolver = new DnsResolver();
         return resolver.getIpAddressFor("wackapedia.org").equals("123.456.78.9");
      }
   }
So now I can call DeDoper.wackapediaOkToday() every day, and find out if it's IP address has changed.  Yay for me.

But now DeDoper depends on (is tightly coupled to) the specific class DnsResolver -- which certainly has to go out and "talk to the outside world".  So my class is totally untestable.

Make a Mock(ery)
OK, so that's not so hard to fix.  I'll just pass (inject!) a DnsResolver in through the class constructor, and EasyMock it during the test:
   public class DeDoper {
      private DnsResolver resolver;
   
      public DeDoper (DnsResolver resolver) {
         this.resolver = resolver;
      }
         
      public boolean wackapediaOkToday() {
         return resolver.getIpAddressFor("wackapedia.org").equals("123.456.78.9");
      }
   }
But now there are two more problems.  The easy one is, "what if I have different kinds of DnsResolvers?".  Say, one for the U.S., and one for China.  (Or, better, Switzerland.)  That's, of course, what interfaces are for.  So I should have a DnsResolver interface, and (at least) a DnsResolverImpl class.

Turtles all the way down...
But the bigger problem is that I've just shoved my issue with 'new' up one level, to whomever calls DeDoper.  So I'll have to solve it all over again there.  Of course, I can make that class push it back up another level... but
  1. that never really solves the problem, and
  2. it can make each level's constructor get bigger and bigger (more and more arguments). 
It's Turtles all the way down (or "up", in this case).
 

IV. The First IOC -- the Factory Pattern
The "classical" solution to this larger problem was the Factory pattern.  Instead of using 'new' to create an object, or getting it passed down "from on high", we ask an object-that-creates-objects to make one for us: i.e. a "factory".
public class DeDoper {
   public boolean wackapediaOkToday() {
      DnsResolver resolver = 
         ResolverFactory.getInstance().makeResolver();
      return resolver.getIpAddressFor("wackapedia.org")
         .equals("123.456.78.9");
   }
}
The beautiful part of the ResolverFactory is that only it knows how to make a DnsResolver.  It might use a configuration file, an environment variable, or even user input to decide what kind of DnsResolver to make.  It might have a pool of DnsResolvers and hand us one of them that is not in use.  A unit-test could even tell the ResolverFactory what kind of mock DnsResolver to provide.

Many Factory's are implemented as a singleton pattern, hence the "getInstance()" call above -- although that is not a strict requirement.

This version of WackapediaOkToday is, very loosely speaking, "injected" with a DnsResolver (although it's admittedly less like getting a shot, and more like asking a waiter for the check).  But it does solve the testing problem, and the "turtles all the way down" problem.

Chained to the Factory
But in this approach, we are literally "chained" to the Factory classes.  (Worse, if the objects created by our Factories have dependencies in turn, we may have to introduce new Factories inside our Factories.)  We haven't fully "inverted" our control, we're still calling (controlling) the Factories from inside our classes.

What was needed was a way to get rid of the control in our classes entirely, and have them told what they were getting (for their dependencies).
 

V. Real IOC
"Real" IOC began around 1998 with the "Avalon" project, although there were hints as early as 1994 in the "Gang of Four's work, like the "Hollywood" ("Don't call us, we'll call you") design pattern.  There's an interesting history of IOC tools at picocontainer.org/inversion-of-control-history.html.  The common element to all of them was some container or configurator that created the needed dependencies, and some way of marking which application objects needed those dependencies.

The early tools, like the first versions of Spring, used XML to define the configuration (of what got created) and injection (where the created objects went).  The downside of the XML approach was the "magic" involved -- it was possible for a person reading the (Java) code to have absolutely no idea where the dependencies were coming from.

Later tools, such as Google's Guice and Tapestry, ditched the XML approach and use pure Java code and annotations to handle configuration and injection.  There's still a certain amount of "magic" in the way the annotations work -- but it is all ultimately traceable to the Java classes that define the configuration.
 

VI. Tapestry IOC 101
Tapestry is primarily a web-application framework, which is heavily dependent on its own built-in IOC container.  Tapestry IOC is similar to (and credits) Guice in its approach, but has its own 'take' on some interesting problems.  You can use just the IOC container if you want (i.e. your app doesn't have to use or know anything about the web framework).

The simplest version of our DeDoper example might look like this in Tapestry:

   public class AppModule {
      public static void bind(ServiceBinder binder) {
         binder.bind(DnsResolver.class, DnsResolverImpl.class);
      }
   }
   
   public class DeDoper {
      @InjectService private DnsResolver resolver;
   
      public boolean wackapediaOkToday() {
         return resolver.getIpAddressFor("wackapedia.org").equals("123.456.78.9");
      }
   }
Only One Magic XML
Tapestry has one piece of XML configuration.  In the WEB-INF/web.xml file, the <filter> tag defines the name of the entire application.  Tapestry looks for a class called nameModule, in our case "AppModule".  All of the rest of the configuration is done in Java code in that class.  So the bind method says that "if you need an instance of the interface DnsResolver, make a (singleton!) DnsResolverImpl".

Now DeDoper gets its DnsResolver injected, and it doesn't know (or care) where it comes from (or in this case, the fact that it's a singleton).  By the time DeDoper is instantiated, it "has" a DnsResolver.  That's all it cares about.

A Tapestry 'service'
A class like DnsResolver is called a "Tapestry Service".  It has an interface, at least one implementation, and can be injected into the application by Tapestry.  Unfortunately, the word "service" is already terribly overloaded, so this can get confusing.  Me, I'd call it a "service that Tapestry serves up to you" (STTSU?), but that's even worse!

Three Kinds of Injection
The careful reader will have noted that the 2nd version of DeDoper "injected" a DnsResolver through the constructor, whereas the Tapestry example above injected a DnsResolver straight into a field.  There are typically three kinds of injection:
  1. Field injection, as shown above.  (Tapestry actually manipulates the bytecodes at load time to inject into private fields, which is fast.  But unit-tests for the same methods will have to use reflection to inject those fields -- more later!)
  2. Constructor injection, like the 2nd DeDoper.  This is arguably cleaner, and easier to unit-test.  See the next section for an example.
  3. Setter injection, where an actual setter method is called (by some magic somewhere) to set the dependency.  Tapestry doesn't use setter injection, so we'll ignore it.

 
VII. Tapestry IOC 201
The binder.bind() example above is a very terse way of telling Tapestry how to instantiate a simple dependency.  In that case, the DnsResolver had no dependencies of its own, so Tapestry could just go ahead and make one as needed.

Let's extend the DnsResolver so that it does require a dependency: a "DnsServer" that represents an actual, physical, name server.  The good news is, the DeDoper class remains the same.  We're making a major change to how a DnsResolver gets built, but the classes that use it don't need to know about that.  Hurrah for de-coupling!

The configuration for how DnsResolver gets built does get more complicated.  Here's what it looks like now:

   public class AppModule {
      public static DnsResolver build(DnsServer server) {
         return new DnsResolverImpl (server);
      }
   }
Well, that's not so bad.  But where does the DnsServer come from?  Tapestry assumes that it is also a "Tapestry service", and makes one as soon as it is needed, and supplies it in the constructor call above.  (So here's one example of constructor injection.)

Turtles, Redux?
That starts to sound like "Turtles all the way down" again.  Where does DnsServer get what it needs?  Eventually, the chain of dependencies has to stop somewhere.  In this case, maybe I specify the IP address of a DNS server on the java command line with a "-D" variable, and the DnsServer constructor picks up that information from System.getProperty(). 

But the real point is not stopping the chain (or the pile of turtles).  It's the de-coupling of knowledge of the dependencies, from the classes that use them.  DeDoper doesn't need to know anything about DnsResolver's dependencies, or even how it is constructed.  DnsResolver doesn't need to know anything about how DnsServer is built.  And so on.

 
VIII. Tapestry IOC 301: multiple implementations of a service
Tapestry provides a sometimes bewildering array of features in its IOC container.  There's often more than one way to do something (e.g. build a Tapestry service, as in sections 101 and 201 above), and the amount of "magic" annotations increases with each option. 

The official Tapestry IOC documentation is comprehensive, but not always comprehensible without careful study and much back-and-forth reading.  That's part of the purpose of this article, to provide a starting path through the thicket of the different uses of IOC in Tapestry.  If you've worked through this article, then the real IOC doc will make much more sense.

So the next step along this path is: "what if I have multiple implementations of a "Tapestry service"?  How does Tapestry know which one I want, when it's being injected?

"Service ids"
Tapestry has the notion of "service ids".  By default, the "id" of a service is the same as the name of the interface class.  So the id of the DnsResolver's built by DnsResolverImpl, is the String "DnsResolver".

If I wanted to have a second implementation of DnsResolver, say DnsResolverForChinaImpl() that checked DNS resolution behind the "Great Firewall of China", I could do either:

   public class AppModule {
      binder.bind(DnsResolver.class, DnsResolverForChinaImpl.class)
         .withId("ChinaDnsResolver");
   }
or
   @ServiceId("ChinaDnsResolver")
   public class DnsResolverForChinaImpl implements DnsResolver {
      ...
   }
Now when I want to use the China resolver, I can specifically identify the service id.  Here's an updated version of DeDoper that gives me two choices:
   public class DeDoper {
      @InjectService
      private DnsResolver defaultResolver;

      @InjectService("ChinaDnsResolver")
      private DnsResolver chinaResolver;
   
      public boolean wackapediaOkToday() {
         return defaultResolver.getIpAddressFor("wackapedia.org").equals("123.456.78.9");
      }

      public boolean wackapediaOkTodayInChina() {
         return chinaResolver.getIpAddressFor("wackapedia.org").equals("123.456.78.9");
      }
   }

 
IX. Unit-testing and injection
Back in section II I said it all starts with unit-testing.  So what does a unit-test with Tapestry IOC look like?  It has a dirty little secret...

   public class DeDoperTest() {
      @Test
      public void shouldDetectWackapediaIsOkToday() {
         DnsResolver mockResolver = 
            EasyMock.createMock(DnsResolver.class);
         expect (mockResolver.getIpAddressFor("wackapedia.org"))
            .andReturn("123.456.78.9");

         DeDoper deDoper = new DeDoper();
         FieldUtils.injectTapestryService (deDoper, "defaultResolver", mockResolver);

         replay(mockResolver);
         boolean okToday = deDoper.wackapediaOkToday();
         verify(mockResolver);

         assertTrue(okToday);
      }

   }
The "dirty little secret" is the injectTapestryService() call.  Java "best practice" is to make most instance fields private... and the Tapestry web framework usually requires private fields for injected web pages and components.

So our unit-tests have to completely break the rules, and use reflection to manually inject mocked objects (like mockResolver above) into the private fields.  This works for now... and injectTapestryService() as written checks to make sure there is an Inject annotation on a field.  Reflection is slow... although that doesn't matter so much for a unit-test.  But it seems like there should be a better way.

Side notes:

  1. Yes, this test is pretty stupid.  That's because there's not much behavior in wackapediaOkToday() to test.  But it's easy to imagine more business logic that could be in that method, and then the test starts being useful.
  2. The injectTapestryService() method is not part of Tapestry -- it was written locally.  But anyone using Tapestry IOC is going to end up writing something similar.  For reference, here's part of it:
       public static  T injectTapestryService(Object obj, String fieldName, T serviceImplementation) {		
          Class target = obj.getClass();
    
          // Find the field associates with the property name
          Field field = findField(target, fieldName);
          if (field == null) {
             throw new FieldUtilsException("No such field: " + fieldName);
          }
    
          // See if the @Inject annotation is present			
          if (!hasInjectAnnotation(field)) {
             throw new FieldUtilsException("An injectable annotation was not found on the field <" + fieldName + ">");
          }
    
          return setField(obj, field, serviceImplementation);		
       }
    

 
X. Appendix: Questions to Ask

  1. If I'm not writing a web application, how does Tapestry know what my Module class is?  (Since there's no web.xml...)
  2. I'm still confused about @Inject vs. @InjectService.  Both will work for many cases, but what is the real difference, and why use one or the other?
  3. JSR 330 is a new standard that may subsume the different IOC's ways of annotating DI.  Should we start using it, and how?
  4. Others?