Sunday, January 29, 2006

 

The basket design pattern

We all know that some cross-cutting concerns can be nicely tackled via AOP. A school example of this can be found in the ejb3-interceptors-aop.xml file from JBoss Application Server, where you encounter declarations like:

<bind pointcut="execution(* @org.jboss.annotation.security.SecurityDomain->*(..))">
<interceptor-ref name="org.jboss.ejb3.security.RoleBasedAuthorizationInterceptorFactory"/>
</bind>
<interceptor factory="org.jboss.ejb3.security.RoleBasedAuthorizationInterceptorFactory" scope="PER_CLASS"/>

AOP is really great when it comes to aspects like authentication, authorization, transactions, remoting, logging, validation. An aspect is all about creation and execution of some additional code along the path of invocation on an object.
A very important property here is that in general the advice is unaware of the actual state of the object over which it is applied. Most of the time the advice even doesn't care about the type of its cutting objects since it can use annotations on the type to find out what to do.

Unfortunately, there are "data aspects" that require some kind of help of the objects itself to be able to perform its task. A nice example of this is exporting data related to a certain (business) entity. Suppose your application defines person entities. These persons create different data artifacts during their life-cycle. For example reports, invoices. These data artifacts, which are all somehow related to a certain person, are scattered all over the model of your application. How are you going to implement the person export service?

One can go for a big, fat and centralized export component that invokes an export method on all components that hold data related to a certain person for which you want to export data. Thus this export component pulls the data out of the system. Great, it will work. No problem. Except when you add a new service that allows the person to generate additional exportable artifacts. Then you should not forget to also update the centralized export component so that these new artifacts get exported when requested. Who's going to keep track of that? Keeping the centralized export component in sync all the time is difficult. It smells bad. The problem that we have with such a design is no good SoC.

A funny way to solve this problem is by using the, what I call, basket design pattern. In this design pattern different components within a system participate in filling a basket for a certain data aspect like exporting, backup. The basket initially contains just a ticket stating what should go in it. Then this basket is passed along all participating components until it's filled up. It's like shopping. A participating component can put both data and new tickets into the basket. The tickets can be used by other components to generate data and/or yet other new tickets.

The basket is managed by a basket producer. This component runs over the basket fillers until the basket no longer changes content.

boolean changed;
do {
changed = false;
for (BasketFiller basketFiller : basketFillers) {
changed |= basketFiller.fillBasket(basket);
}
} while (changed);

Basket fillers indicate that they want to participate in filling a basket of a certain type by registering themselfs on the basket producer. In JBoss EJB3 this can easily be done via JNDI. A basket filler, which is just a @Stateless bean implementing a BasketFiller interface, registers itself to the basket for exporting via @LocalBinding(jndiBinding = "baskets/export/myname"). The basket producer can lookup the objects via new InitialContext().list("baskets/export") and then start running over the basket fillers. Unit testing the basket producer can easily be done by using shiftone-oocjndi, but beware of the Mock language!

One interesting aspect of the basket is the ticket. This is the medium for the participating components to communicate, even if they don't really know about one another. Thus each basket type defines a set of ticket types that can be used during the basket shopping. As for the data; each participating component can put whatever data into the basket.

A nice feature of this design pattern is that you have the SoC back again; the component generating an artifact is also responsible for the export aspect of it. This implies a decentralized push design. And, you can clearly scope the communication medium for a certain basket type by means of the set of ticket types. Thus it allows you to make a certain "data aspect" manageable again.

Comments: Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?