sixlegs.com / blog / java / pico-bandwagon.html

Root Beer Logo Root Beer

Chris Nokleberg's Fizzy Weblog

June 2003
Su M Tu W Th F Sa
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 19 20 21
22 23 24 25 26 27
29 30
Previous  |  Next  |  More...
#  The PicoContainer Bandwagon

I'm a bit reluctant to admit this given how much other people seem to like it, but I have not yet climbed aboard.

Part of the problem is that I'm not really sold on Inversion of Control (IoC) in general, at least for a services framework. But I also have a few issues with PicoContainer itself. So this is going to be a bit of a rant, but hopefully also a starting point for discussion.

To get a feel for the trade-offs I'd like to compare a few different scenarios. In all of them there is the standard interface/implementation separation for the services in question:

  1. IoC: Service passed via Constructor (aka PicoStyle)

    public class MyCommand implements Command {
        private Service1 s1;
        private Service2 s2;
        public MyCommand(Service1 s1, Service2 s2) {
            this.s1 = s1;
            this.s2 = s2;
        }
        public execute() {
            doSomething(s1, s2);
        }
    }
    
  2. IoC: Service passed via JavaBean setter (aka BeanStyle)

    1. Using marker interface
    2. Using metadata (e.g. JavaDoc tags)
    3. Using reflection
    public class MyCommand implements Command {
        private Service1 s1;
        private Service2 s2;
        public void setService1(Service1 s1) {
            this.s1 = s1;
        }
        public void setService1(Service1 s2) {
            this.s2 = s2;
        }
        public execute() {
            doSomething(s1, s2);
        }
    }
    
  3. NON-IoC: Service retrieved via static method (aka ThreadLocalStyle)

    public class MyCommand implements Command {
        public execute() {
            doSomething((Service1)ServiceContext.get(Service1.class),
                        (Service2)ServiceContext.get(Service2.class));
        }
    }
    

"Advantages" of PicoStyle over BeanStyle:

  • Safer, since if you've constructed the object you can be sure all dependencies are satisfied

    True, except for nulls, which in my mind invalidates this argument. In the BeanStyle you could still have an "Initializable" interface with a "void initialize()" method called by the container, and you would perform your null checks there.

    Plus, the configuration mechanism is clumsy--you have to supply the non-service parameters in the exact order they are declared in the constructor. This is error prone and annoying. One very nice thing about JavaBeans is that the properties have names!

  • No need for metadata or marker interfaces

    That sounds good, but I've never understand the need for these anyway. Why can't we just look for bean properties that have types of registered services?


"Advantages" of IoC over ThreadLocalStyle:

  • Works better for single invocations spread over multiple threads

    Hard to argue with this one, but I've never seen an example in the real world. I assume someone has? I'm not sure this solved the problem entirely, but you can use InheritableThreadLocal to pass your ServiceContext to forked threads.

  • Easier to discover dependencies, using reflection.

    If it's to produce pretty pictures of the dependency graph, suffice it to say I'm unconvinced. I can see an argument for performance, since the container could generate optimized code for passing services to either a constructor or bean setter method. But:

    • In the naive case, it will be slower, since reflection is even slower than a ThreadLocal lookup.
    • You will always be passing the full set of services possibly required, even though given the logic of the code they may not actually be used.

    That said, this does increase the likelihood for byte code generation to be of use, so I've got to give it some credit.

  • Easier unit/mock testing

    Unit testing is a bit easier with IoC, since you pass all of the necessary services to the object being tested. But it should not be hard in the ThreadLocalStyle to populate your ServiceContext with the appropriate services or mocks, either, given that there is still an interface/implementation separation.

    Even with ThreadLocalStyle you could temporarily override a service from within a component, for example:

    public class MyCommand implements Command {
        public execute() {
            Object save = ServiceContext.get(Service1.class);
            try {
                doSomething();
            } finally {
                ServiceContext.set(Service1.class, save);
            }
        }
    }
    
  • Type safety, removal of casts

    The actual "safety" of it seems a bit overblown to me, since in a test-driven environment you'd find out pretty quickly via a ClassCastException that you're getting the wrong service.

    But I do not particularly care for the casts in the ThreadLocalStyle either. Although from the example above you can see that it is less actual typing than either of the IoC methods, it still looks ugly.

    However, you can easily remedy this by providing type-safe service lookup methods in a static utility class:

    abstract public class Services {
        public static Service1 service1() {
            return (Service1)ServiceContext.get(Service1.class);
        }
        public static Service2 service2() {
            return (Service2)ServiceContext.get(Service2.class);
        }
    }
    

    At which point the ThreadLocalStyle example becomes:

    public class MyCommand implements Command {
        public execute() {
            doSomething(Services.service1(), Services.service2());
        }
    }
    

    Also, this example really should be

    public class MyCommand implements Command {
        public execute() {
            doSomething();
        }
    }
    

    since MyCommand itself does not need the services, and the doSomething method can just ask for the ones it wants. Which brings me to...

Final Thoughts

My last complaint (for now) is that when using IoC you have to make some decision about "what is a component". Wherever you draw that line, there is going to be finer-grained non-component objects, or methods the component calls (business logic). What if they need functionality provided by services? Either you make every object in your JVM a component (I'm joking), or the components must pass all the necessary services to any non-components or methods that need them. And they to the methods they call, etc.

In my experience, often "services" are orthogonal to your class design, yet the IoC style of passing services around obviously affects your method signatures. This could actually decrease the amount of code packaged into reusable services, due to the pain of refactoring, or existing API contracts. For one thing, with a ThreadLocalStyle it becomes much more feasible to get rid of your static utility functions and package them into pluggable services, even after the fact.

Comments welcome!

[Powered By FreeMarker]  [Valid Atom 1.0]  [Weblog Commenting by HaloScan.com]