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.
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.
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.
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.
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?
Posted by Bo on December 01, 2003 at 01:30 PM EST #