Technical Articles by Sony Mathew, Software Architect, Twin Cities, MN


Friday, November 27, 2009


Context IoC Revisted


I wrote about the Context IoC Design Pattern a while back now (Feb-2005) and some people picked up on it right away.  I even caught a C++ equivalent of Context IoC on the google testing blog site. I have also seen similar patterns used in Scala and other languages.  IoC containers are still popular (mostly for Java) and new ones have come on the scene like Guice which use Annotations to address many of the concerns I had initially outlined.

Here are some of the arguments for Context IoC I've made in the past:

1) There simply isn't a better language for describing and instantiating Java objects and invoking Java methods than Java itself !!!  Use of a container for IoC means all construction and dependency semantics have to be described using secondary configurations (XML or Annotations) which are then interpreted and implemented at runtime.  They are inherently going to be slower and more complex than direct Java construction and wiring.

2) The big advantage of Context IoC is type safety.  Context IoC Design Pattern does NOT use string identifiers to wire dependencies together.  Annotations can eliminate this problem as well if done right by an IoC Container Framework.

Comparing against Annotation based Dependency Injection

I'm still mixed about Annotation based Dependency wiring.  Annotations are a fabulous feature and I had fully expected them to change the landscape of containers - but its intent was for containers to post-process code as an after-thought to provide deployment level mappings rather than be used as the central engine for wiring our code.  Annotations can be processed differently environment to environment and left to container interpretations which is not appropriate for application flow in my opinion (unless of course you sell your soul to a single container maker).


In any case Annotations do address some of the issues I outlined against XML|String wired IoC containers - so Let us compare Context IoC and Annotation based Dependency Injections (DI).

Annotation based Dependency Injection:

  public class MyService implements SomeService { 
 
    private CommonService blue = null;
    private CommonService red = null;

    @Inject //imported from com.third.party.ioc.container
     public void setBlue(@Blue CommonService blue) {
       this.blue = blue;
     }
     
     @Inject //imported from com.third.party.ioc.container
     public void setRed(@Red CommonService red) {
       this.red = red;
     }

  }

Context IoC based Dependency Injection:

  public class MyService implements SomeService {    
    
    public interface Context {
       CommonService getBlue();
       CommonService getRed();
    }

    private final Context cxt;

    public MyService(Context cxt) {
      this.cxt = cxt;
    }
         
  }

Not much different in expressiveness.  With the Annotation based framework however you create a binding using the framework's proprietary classes that read the annotations at runtime and interpret your intentions to create the appropriate wirings.  With Context IoC you own your bindings - just implement your Contexts. You can create your bindings per package or per module or per application.  Your bindings merely implement all the Contexts you choose to assemble as a module or application.  With this approach you can refactor your Contexts and Bindings,  generalize them or decompose them - it is just Java, it is just OO and best of all it is your code, not extending some proprietary framework sprinkled with proprietary annotations.  

Annotation based Binding: 

com.third.party.ioc.container.ProprietaryBinding binding = new com.third.party.ioc.container.ProprietaryBinding();

binding.bind(SomeService.class)
     .to(MyService.class);

binding.bind(CommonService.class)
    .annotatedWith(Blue.class)
    .to(BlueService.class);

binding.bind(CommonService.class)
  .annotatedWith(Red.class)
  .to(RedService.class);

Context IoC Binding:

public class MyBinding implements MyService.Context {  
  private final SomeService main;
  private final CommonService blue;
  private final CommonService red;
  
  public MyBinding() {
    main = new MyService(
this); //pass 'this' as context.
    blue = new BlueService();
    red = new RedService();
  }
 
  public SomeService getMain() {
    return main;
  }

  public CommonService getBlue() {
    return blue;
  }
  
  public CommonService getRed() {
    return red;
  }  
}

Both bindings can be compile safe in Java, however, compile safety in the Annotation based binding is provided via the 3rd-party container's ProprietaryBinding whose method signatures enforce compile safety via generics, while the Context IoC binding is pure and simple Java and reliant only on your IDE or Java compiler for safety and not on an API to enforce compile safety (or for that matter bind your objects).

How about if additional dependencies were needed - say BlueService needs yet another CommonService?

Add a dependency via Annotation: 

  public class BlueService implements CommonService {  
      private CommonService green = null;

    @Inject //imported from com.third.party.ioc.container
     public void setGreen(@Green CommonService green) {
        this.green = green;
     }
    
  }

Add a dependency via Context IoC: 

  public class BlueService implements CommonService {    

    public interface Context {
       CommonService getGreen();
    }

      private final Context cxt;

    public BlueService(Context cxt) {
        this.cxt = cxt;
    }
         
  }

And the bindings would be updated to the following:

Annotation based Binding:

  ProprietaryBinding binding = new ProprietaryBinding();

  binding.bind(SomeService.class)
      .to(MyService.class);

  binding.bind(CommonService.class)
      .annotatedWith(Blue.class)
      .to(BlueService.class);

  binding.bind(CommonService.class)
    .annotatedWith(Red.class)
    .to(RedService.class);

  binding.bind(CommonService.class)
    .annotatedWith(Green.class)
    .to(GreenService.class);

Context IoC based Binding:

public class MyBinding implements MyService.Context, BlueService.Context {  
  private final SomeService main;
  private final CommonService blue;
  private final CommonService red;
  private final CommonService green;
  
  public MyModule() {
    main = new MyService(this);
    blue = new BlueService(
this); //pass 'this' as context.
    red = new RedService();
    green = new GreenService();
  }

  public SomeService getMain() {
    return main;
  }
 
  public CommonService getBlue() {
    return blue;
  }
  
  public CommonService getRed() {
    return red;
  }

  public CommonService getGreen() {
    return green;
  } 

}

Your friendly neighborhood IDE makes Context IoC clearly better!
Your IDE is your friend, and with Context IoC, as soon as you add a dependency to any Object's Context - your IDE lets you know all the bindings that need to be updated and fixed.  You may have one or more bindings in your application depending on how you modularize and of course one or more Unit-Test bindings as well that may need to be updated and fixed.  Here Context IoC is clearly better since an Annotation or XML based binding will not help identify these mistakes until you execute your App.  Annotation based bindings cannot provide compile safety if you never made a binding entry in the first place!  But your good old Java compiler and IDE will come to your rescue with Context IoC and tell you where all you have forgotten to provide a binding entry.

Wrappers|Decorators|Interceptors

Annotations should be used for what they are designed - provide external environmental binding points.  Annotations can now be post-processed by Wrappers|Decorators|Interceptors of your choosing based on how an application should be configured and deployed to an environment.  All forms of IoC Bindings (be it Context IoC or Annotation based or XML based) can easily allow for Wrappers or Decorators or Interceptors since any of these Bindings manage Object construction.  Here again, If you are using a 3rd party IoC Container to manage your IoC Bindings - you will need to use their proprietary API or proprietary declaration to hook in your Decorator or Interceptor. (Note: Standardization can help alleviate some of this).