« Thanks. | Main | One of my issues. »

Refactoring Roller's persistence architecture.

Roller has changed quite a bit since the Roller article was published on the O'Reilly site. Roller has moved from a two layer architecture with a Presentation Layer and a Business Layer to a three layer architecture with Presentation, Business, and Persistence Layers. This article covers the transition and will bring you up to date with the current Roller persistence architecture.

Then, as now, the Roller Business layer was a small collection of interfaces. The Roller interface was the entry point and provided access to the various Roller manager interfaces. Back then, the Business Layer interfaces were implemented in the org.roller.business package and used the Castor O/R mapping framework APIs directly. The diagram below illustrates this point. The org.roller.business package implements the Business Layer intefaces defined in org.roller.model and depends on org.exolab.castor.

<img src="http://www.rollerweblogger.org/resources/roller/roller-2layer.png" alt="Roller's original 2-layer architecture" />

Earlier this year, during Roller 0.9.7 development, we decided to create an implementation of the Roller Business Layer using the Hibernate O/R framework. We moved the Castor implementation into the org.roller.business.castor package and added the new Hibernate implementation in org.roller.business.hibernate. In the process we noticed a lot of code duplication and we tried to move common code back up into org.roller.business, but we had limited success. The diagram below illustrates this situation: separate Castor and Hibernate implementations of the Roller Business Layer with some shared code.

<img src="http://www.rollerweblogger.org/resources/roller/roller-2impls.png" alt="Two implementations of the Business Layer interfaces" />

Since Roller 0.9.7, the Roller Business Layer implementation has been evolving and moving towards that three layer architecture mentioned at the start of this article. We introduced a PersistenceStrategy interface and a persistence package in org.roller.persistence. Over time we have slowly moved almost all Castor and Hibernate specific code behind the PersistenceStrategy interface. I say almost because, there are a couple of cases where the Persistence Layer abstraction is not powerful enough to handle the queries needed by the Business Layer. Specifically, the RefererManager.getHits() and RefererManager.getDaysPopularWebsites() methods need to drop down to raw SQL because, apparently, neither Castor nor Hibernate can handle the queries we need to do.

The diagram below illustrates the current situation. The Business Layer calls upon the Persistence Layer for object storage, retrieval, and queries. The org.roller.business.castor and org.roller.business.hibernate packages are shown in grey because they still exist but are on the way out.

<img src="http://www.rollerweblogger.org/resources/roller/roller-3layer.png" alt="Two implementations of the Business Layer interfaces" />

Thus far, we've been looking at things at the package level. Let's zoom in on the org.roller.persistence package and take a close look at the key interfaces in that package: <a href= "http://www.rollerweblogger.org/javadoc/org/roller/persistence/PersistenceStrategy.html"> PersistenceStrategy, <a href= "http://www.rollerweblogger.org/javadoc/org/roller/persistence/QueryFactory.html"> QueryFactory, and <a href= "http://www.rollerweblogger.org/javadoc/org/roller/persistence/Query.html"> Query.

<img src="http://www.rollerweblogger.org/resources/roller/persistence-api.png" alt="Roller persistence and query API interfaces" />

PersistenceStrategy supports storage, retrieval, and removal of objects. To execute a query, you create a Query object using the QueryFactory, create a where clause by adding Conditions together, set a limit if desired, set an order-by if desired, and call the Query.execute() method. At that point, the Query implementation will generate an HQL query for Hibernate or an OQL query string for Castor and will call the appropriate method to execute the query. Below is some example code to illustrate typical usage of the Query API.

    public List getTodaysReferers(WebsiteData website) throws RollerException {
        if (website==null ) throw new RollerException("Website is null");
        QueryFactory factory = mStrategy.getQueryFactory();
        Query query = factory.createQuery(RefererData.class.getName());                
        Condition specifiedUser = factory.createCondition(
            "website", Query.EQ, website);            
        Condition hasDayHits = factory.createCondition(
            "dayHits", Query.GT, new Integer(0));            
        Condition userEnabled = factory.createCondition(
            "website.user.userEnabled", Query.EQ, new Boolean(true));            
        List conditions = new LinkedList();
        conditions.add(specifiedUser);
        conditions.add(hasDayHits);
        conditions.add(userEnabled);                
        query.setWhere(factory.createCondition(Query.AND, conditions));            
        query.setOrderBy(Query.DESC, "dayHits");       
        return query.execute();
    }

The PersistenceStrategy interface is a fairly simple abstraction, sort of a least common denominator approach, but it is expressive enough to handle all of the requirements of Roller. This approach allows the Roller Business Layer to be completely independent of the underlying persistence mechanism. This is really nothing new. PersistenceStrategy is essentially a generic Data Access Object (DAO). I use the word "generic" because most DAOs I've seen are specific to one type of object, a CustomerDAO or EmployeeDAO for example, but PersistenceStrategy handles any persistent object in the system. I'm interested to see how this approach holds up. Will we eventually find the simple API to be too limiting? Will it be possible to implement PersistentStrategy with other persistence technologies such as JDO?

Comments:

Dave, This is an interesting approach. You're right that the ${Type}Dao approach is more common but there are serious issues with the type-based DAO approach like (1) it can easily lead to class-explosion where the number of DAOs grow without end (2) it can lead to a kind of brittle-ness where changes in the business objects necessitate changes in the DAO layer (3) a specific query may be intimately concerned with two types and then it becomes difficult to decide where that query belongs. I don't like your particular Query interface (my Query interface would only have a single method, 'execute()' and subinterfaces would expose methods like setWhere) but I think you're on the right track with a generic approach in which queries are objects/actions.

Posted by Bo on December 01, 2003 at 01:30 PM EST #

Post a Comment:
  • HTML Syntax: NOT allowed

« Thanks. | Main | One of my issues. »

Welcome

This is just one entry in the weblog Blogging Roller. You may want to visit the main page of the weblog

Related entries

Below are the most recent entries in the category Roller, some may be related to this entry.