1 – Overview
Many months back, I had the opportunity to refactor a codebase that was less than optimal for a development team of any size to work in. The challenges seemed endless, but the basic roadblocks were in the way of a decent API. Code was sufficiently spaghettified and needed an overhaul to allow productive implementations to be built upon it.
A colleague of mine and myself decided to knock up a sample DAO and Service layer using a little Spring + Hibernate + Java Generics goodness. The idea here was to scaffold enough code to get the team productive and satisfy CRUD use cases and serve up extension points where appropriate. The following is a quick tour through the DAO and Service layer.
2 – The Base DAO
The DAO layer is straightforward enough. We are using generics here to parameterize the domain objects that a given DAO will manage. The idea here is that a DAO will manage one domain object in most cases. If the DAO needs to manage more than one domain object, then it needs to be extended and implemented to do so. Following the 80-20 rule, this addresses the 80.
Methods on the Base DAO consist of CRUD operations with fancy finders and a pagination method thrown in for good measure. The Base DAO uses the Hibernate template as the underlying data source façade. This is an assumption, but in our case, a fairly decent one. Hibernate is an abstraction layer. If portability were an issue, it would be dealt with at the Hibernate level.
A couple of interesting things on the Base DAO. We added a series of methods to help Hibernate fixup join paths when performing queries. All mappings are defined as lazy by default. If a use case requires additional joins, then the caller should add these to the join path arguments available on the various finder methods. Open Session In View is a terrible pattern that gets you into hard to debug places. We want the developer to manage their use case and let Hibernate drive when it can.
Additionally, a couple of convenience methods were added to help with the occasion where developers need to knock up a paginated list of domain objects with counts. In order not to pollute namespace imports, a String map is used to pass sort by criteria that gets built into Hibernate order bys and criteria. Pagination is implemented by delegating down to Hibernate’s ability to do pagination.
Outside of the fluff, the Base DAO is parametric. We use Hiberante metadata to get the identity property name for the findById methods.
That’s the DAO layer in a nutshell. CRUD is delegated to the Hibernate Template instance and we simply wrap the rest.
3 – The Base Service Layer
The service layer is the layer that gets the most attention. The service layer often organizes one or more DAOs to get a unit of work completed. Our service layer uses a combination of convention and Spring goodness to bootstrap CRUD operations.
Although we encourage service classes to implement CRUD operations on each impl’s interface, it becomes redundant. It’s sometimes convenient to access ANY domain object via a single service impl. A Base Service class was established to do just this. This is accomplished by allowing the service layer become BeanFactoryAware. The base service class gets a handle to a Spring bean factory which allows it to access DAOs. Naming convention allows us to retrieve a given DAO by way of the domain objects. For example:
findEntityById(Class clazz, Serializable Id) on the base service class attempts to find the supporting DAO for the class argument. Naming convention allows us to fixup the supporting DAO’s interface I<DOMAIN OBJECT’s CLASSNAME><DAO SUFFIX> and execute the finder method. Yes, this is cheating a bit… But, developers benefit from having easy access objects without having to worry about excessive coding at the service level. Appropriate exceptions are thrown if the base service layer does not find the supporting DAO. If you need specialization, overriding the base class will do just fine.
Yes, the arguments on binding to Spring are present. However, the benefits are worth more than the dependency binding. You could argue the by implementing a registry pattern you get around the binding to Spring’s bean factory. But, we’re somewhat committed to using Spring, so this really isn’t an issue.
For the most part, the service API wraps the underlying DAOs. This allows us to declare transaction boundaries at the service tier where it belongs.
4 – Domain Objects
Domain objects are simply sourced at the Hibernate mapping level. We use a strategy where Hibernate mappings are used to generate base domain objects. This allows us to extend the base domain object to add helper or convenience methods to the extended classes. There are a number of benefits in doing this:
- Domain Object changes are easily regenerated. All we have to do is run a clean build to pick up new changes in the domain model. New fields are present in the base class and immediately available to the rest of the API.
- Naming convention is enforced at the mapping level. This serves as a control point for domain object generation. Good mappings result in good domain objects.
- Code generation is leveraged resulting in fewer typos.
- Change is simpler to introduce.
Again, naming convention is used to identify base domain objects. Base<Domain Object Name> is used to identify base level domain objects. Extensions simply drop the Base prefix. This keeps the model tidy since ONLY the extensions are used. Base objects are generated as abstract to prohibit instantiation.
I mentioned code generation as part of the domain model generation. A couple of POJO freemarker templates were created to facilitate domain object generation. Additionally, a few freemarker templates were created to set up builder patterns for domain objects. Builders are primarily used in unit testing to eliminate noise, and improve readability, in the testing code. A little code generation can go a long way J
5 – Conclusion
Yes, Groovy does scaffolding way better than this combination of things. But when it comes to getting a team up and running fairly quickly, its amazing what a little convention, code generation, and base lining of an API will do. This was simply a brain dump of some of the techniques used. I’ll provide code examples shortly…