Readers of my blog must have been bored by now with my regular chantings for the necessity of a generic data access layer in Java based applications. I have designed one which we have been using in some of the Java projects - I have blogged extensively about the design of such an artifact here, here and here. The DAO layer has been designed as a Bridge with a dual hierarchy of interfaces acting as client contracts backed up by the implementation hierarchies. So long the clients had been using the JDBC implementation and have never complained about the contracts. Only recently I thought that I will have to sneak in a JPA implementation as well, since Spring has also started supporting JPA.
Things fell into place like a charm, till I hit upon a roadblock in the design. If u need to provide some contracts which make sense for some specific implementation (not all), then what do u do ? The basic premise of using Bridge is to have a single set of interfaces (contracts) which all implementations need to support. We have the following options :
- Throw exceptions for unsupported implementations and hope the user does not use 'em. Document extensively warning users not to venture into these territories. But if my client is like me and does not have the habit of reading documentations carefully before coding, then he may be in for some surprises.
- Use the Extension Object Design Pattern, which allows u to extend an object's interface and lets client choose and access the interfaces they need. Cool - this is what I need to extend the contract of my generic DAO ! But hold on !! Look at the very first line of the pattern's intent, as described by Erich Gamma .. "Anticipate that an object’s interface needs to be extended in the future.". What this means is that u will have to design your abstraction anticipating a'priori that it may be extended. So if the necessity of providing extensions is an afterthought (which is, in my case), then it doesn't fit the bill.
Extension of the Generic DAO Contract
One of the nifty features of EJB QL is that the user can specify a constructor within the SELECT clause that can allocate non-entity POJOs with the set of specified columns as constructor arguments. Let me illustrate through an example shamelessly copied from Richard Monson-Haefel and Bill Burke's Enterprise JavaBeans book.
public class Name {
private String first;
private String last;
public Name(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() { return first; }
public String getLast() { return last; }
}
Note that
Name
is NOT an entity. Using EJB QL, we can actually write a query which will return a list of Name
classes instead of a list of Strings.SELECT new com.x.y.Name(c.firstName, c.lastName) FROM Customer c
I wanted to provide a contract which can return a collection of objects belonging to a different class than the Entity itself :
public <Context, Ret> List<Ret> read(Context ctx,
String queryString,
Object[] params);
And I wanted to have this contract specifically for the JPA implementation.
Dynamic Extension Objects using Inter-type Declarations in Aspects
Inter-type declarations in aspects provide a convenient way to declare additional methods or fields on behalf of a type. Since I have been using Spring 2.0 for the JPA implementation of the DAO, I went in for Spring Introductions, which allow me to introduce new interfaces (and a corresponding implementation) to any proxied object.
Quick on the heels, I came up with the following contract which will act as a mixin to the DAO layer:
public interface IJPAExtension {
public <Context, Ret> List<Ret> read(Context ctx,
String queryString,
Object[] params);
}
and a default implementation ..
public class JPAExtension<T extends DTOBase> implements IJPAExtension {
public <Context, Ret> List<Ret> read(Context ctx,
String queryString,
Object[] params) {
// ...
}
}
And .. the Weaving in Spring 2.0
The client who wishes to use the new interface needs to define the extension object just to introduce the mixin - the rest is the AOP magic that weaves together all necessary pieces and makes everybody happy.
@Aspect
public class DAOExtension {
@DeclareParents(value="com.x.y.dao.provider.spring.jpa.dao.*+",
defaultImpl=JPAExtension.class)
private IJPAExtension mixin;
}
The original contracts remain unpolluted, other implementations do not bloat, still we have successfully introduced new functionalities in the JPA implementation, still without the client committing to any implementation class (we all know why to program-to-interfaces - right ?). The client code can write the following :
IJPAExtension mixin = (IJPAExtension)restaurantDao;
List<RName> res =
mixin.read(factory,
"select new com.x.y.dao.provider.spring.jpa.RName(r.id, r.name) from Restaurant r where r.name like ?1",
params);
Inter-type declarations are not a very frequently used feature of aspect oriented programming. But it is a useful vehicle for implementing many patterns in a completely non-invasive way. I found it very useful while extending my JPA based DAO implementations without adding to the base contracts of the bridge.
2 comments:
This is sort of a combined response to this post and the 'From Java to Ruby ? Now ? Naah' article.
I find your code quite interesting, however, considering your argument about developers having to understand Ruby paradigms as being a barier to a transition to Ruby, is the code demonstrated easier to grasp and addopt than:
Class Mixined
include mixin_stuff
end
Also, do you consider understanding AOP, not to mention using it effectively, is a more manageable task than picking up a new language with a far more natural syntax and behavior?
Personally, I have noticed more question marks raised by developers when explaining an adaptation of blocks using anonymous inner classes implementing callback interfaces, rather than demonstrating the Ruby equivalent.
[response to george malamidis]:
Learning AOP is not mandatory for today's programmers - in fact there is a school of thought (including some of the big names in the Java world) who feels AOP has not really taken off as a technology. Besides, my argument about the difficulty of Ruby taking the place of Java in today's industry is the mindset of the programmers. It is not the question of easy or difficult - it is related to the change of mindset of the milieu of programmers used to static typing of Java to the world of dynamic languages. This paradigm shift is difficult to scale within the immediate future. If Ruby has all the virtues of being the next language for the masses, eventually it will be realized, but it will take time to apply to the masses.
Post a Comment