Thursday, December 07, 2006

Domain Driven Design : Service Injection Strategies in Spring and their Pitfalls - Part 1

One of the exciting features that Spring 2.0 offers is the ability to inject dependencies into arbitrary domain objects, even when the domain object has not been created by Spring. This is achieved by using the annotation @Configurable on the domain object. I have blogged on this before and had described how this declarative dependency injection helps us in architecting rich domain models.

@Configurable is based on AspectJ's powerful AOP support, though the user can be blissfully oblivious to the nitty gritties of the implementation. In this post, I would like to discuss some of the pitfalls of @Configurable, as it stands today, with Spring 2.0.1. This will open up a discussion towards other strategies of service injection in domain objects using the combination of Spring DI and aspects. I plan to model this to be a nice little series discussing the various service injection strategies, their applications and their pitfalls. Hence the optimistic Part 1 in the title line. Stay tuned ..


@Configurable - Do I need to care about the implementation ?

The Spring reference documentation says
The @Configurable annotation marks a class as eligible for Spring-driven configuration.

This statement gives us a nice declarative semantics for dependency injection into objects not instantiated by Spring. Cool .. but unfortunately as they say, the devil is in the details. And, as a user of this contract, I still need to care about the fact that the semantics of this annotation has been implemented using an aspect. Aspects, provided by the framework, which weave into application code, always tend to be invasive, and there can be side-effects, if I have to plug in my own aspect into the very same domain object. Recently in one of the domain model implementations, I had to introduce explicit precedence on aspects to get my desired functionality :


public aspect Ordering {
  declare precedence: *..*AnnotationBeanConfigurerAspect*, *;
}



So, implementation of @Configurable, may have side-effects on user code. Watch out ..


@Configurable - Not Inheritance Friendly

Spring reference documentation does not mention this explicitly, but the fact is that @Configurable does not handle inheritance correctly. This is related to the implementation mechanism of initialization joinpoints. In case of a pointcut matching an instance with a superclass and a superinterface (e.g. class Implementation extends Parent implements Interface), three initialization joinpoints will be identified - one for the instance (Implementation), one for the superclass (Parent) and one for the superinterface (Interface). Hence the advice will also be executed thrice, once for every matching joinpoint.

@Configurable implementation is based on the AnnotationBeanConfigurerAspect, which is implemented in AspectJ. The following is the corresponding pointcut definition :


public aspect AnnotationBeanConfigurerAspect extends AbstractBeanConfigurerAspect {
  // ...
  protected pointcut beanCreation(Object beanInstance) :
    initialization((@Configurable *).new(..)) && this(beanInstance);
}



while the advice comes from the base aspect :


public abstract aspect AbstractBeanConfigurerAspect extends BeanConfigurerSupport {

  @SuppressAjWarnings("adviceDidNotMatch")
  after(Object beanInstance) returning : beanCreation(beanInstance) {
    configureBean(beanInstance);
  }
  // ...
}



Note that the pointcut is based on the initialization joinpoint of the bean instance. If the annotation is used on a class which has one or more parents in the inheritance hierarchy, then the method configureBean() will be executed once for every matching joinpoint. Now, configureBean() is idempotent - hence the end result will be the same. But still there is a performance overhead for multiple executions of the method.

e.g.


public class Trade {
  // ...
}

@Configurable("cashTrade")
public class CashTrade extends Trade {
  // ...
}



For the above example, issuing a new CashTrade() will invoke configureBean() twice on the instance created.


@Configurable and Deserializability

Deserializing a @Configurable object loses all its injected dependencies. In a clustered environment where serializing and deserializing is a common phenomenon, this is a real problem. Currently the only way to overcome this issue is for the user to write a subaspect of AbstractBeanConfigurerAspect, which takes care of restoring the dependencies on deserialization. While this solves the problem for the user, ideally this should have been taken care of by the framework. The good part is that, Ramnivas has already posted a patch for this problem in the Spring JIRA, which solves the problem almost completely. As he mentions in the comment, the solution will be exactly foolproof with a minor change in the next release of AspectJ.

Apart from the above 3 pitfalls, there are some other problems with @Configurable related to bean location failures for AnnotationBeanConfigurerAspect in hierarchical contexts. Compared to the above three, this is a more specialized occurrence since hierarchical contexts are comparatively rare.

Next time when you use @Configurable, keep an eye on these gotchas. While none of these problems are unsurmountable and we will have the fixes in future releases, these are the pitfalls that we need to consider today when deciding on the service injection strategy for domain objects. In the next part of this series, we will look at yet another strategy for service injection, which uses Spring IoC to inject dependencies into aspects instead of objects. There are situations where we may prefer the latter approach over the more user-friendly @Configurable, but that is the subject of another post, another day ..

1 comment:

Vasily Sizov said...

Perfect post! Thanks for the info!