Close Window

Print Story

Java J2EE Hibernate Extreme Makeover: Architecture Edition

In the past few years there has been a proliferation of frameworks that allow for lighter, faster, and loosely coupled Java projects. These frameworks not only let you decouple your Java project from the application server for unit testing, they also allow for more agile refactoring, testing, and design techniques. This article will focus on telling the story of a large-scale refactoring effort implementing Spring and Hibernate as the underlying infrastructure tools. For those living under an abacus Spring is a J2EE framework built to handle many of the plumbing issues on a typical J2EE application. Hibernate is a popular Open Source Java object/relational persistence framework.

Bad EJB, No Donut
The last few years has produced a legion of J2EE applications tightly coupled to everyone's two favorite saviors, the Enterprise Java Bean (EJB) and the application container. Developers have tied their proverbial hands by coupling their code directly to these two Java mainstays. Many J2EE applications have been over-engineered and deemed overly complex by relying on these mainstays with few architectural best-practice guidelines to follow. Articles have been written and battle lines drawn around why and how this unfortunate series of events came to fruition.

Fortunately, there are now tools that provide ways to enable loose coupling and promote "simpler" architectures. Loose coupling encourages things like unit testing, test-driven development (TDD), and dependency injection. TDD is an approach to development that combines test-first development and constant refactoring. Dependency injection lets you inject dependent objects into your wired Java classes at runtime.

The First Step Is Admitting You Have A Problem
Everyone has probably experienced one or more applications built using the paradigm "'the more J2EE components the better." One of these applications was delivered to my doorstep for an extreme architecture makeover. The application owner's loose requirements were as follows:

Now, I understand that these requirements are ubiquitous requests for most applications and are almost assumed out of the gate. They can almost be seen as "infrastructure" requirements. Still, they're requirements and the right tools need to be used to satisfy them. Putting on my architect hat we came up with five problem areas that were symptoms of why these requirements aren't being met. From these symptoms we derived their root causes and solutions. Table 1 is a matrix showing our findings.

As you can see all of these issues had nothing to do with how the application dealt with business logic. They were solely infrastructure problems that had to be resolved for the application to reach a higher level of software maturity. Follow along as we see what an extreme makeover entails.

Patient Diagnosis - Condition Serious
The application needing the makeover wasn't small and continued to grow every day. A high-calorie diet of EJBs, deployment descriptors and other various J2EE infrastructure files was the norm. It had over 1,600 Java files, 50 entity beans, and over 20 stateless beans (that served mainly as entry points into the application for transactional boundaries). The application's main functions were order provisioning and workflow. It played an integral part in a service-oriented architecture (SOA) that encompassed over 25 Web Services. The software build and deployment cycle to the application server took over 15 minutes. I guess you could call it obese.

There were zero out-of-container unit tests. The only existing tests were written using Cactus, which is a unit-testing framework for executing server-side code. Loosely translated that means that by choosing tools like Cactus your code has to be deployed to the application server to be tested. I personally feel that tools like Cactus enable or even promote bad habits like not having tests that can be run straight from your integrated development environment (IDE).

After reviewing all of the symptoms, a diet of Spring and Hibernate was prescribed. These technologies were primarily chosen for their ability to solve given symptoms. The following is a diary of the patient's miraculous transformation from emergency room to recovery room. Each symptom was typically remedied in one software iteration. The refactoring project took about five iterations over a period of five months, with between 2-4 infrastructure developers working on it at any given time. As the refactoring took place, the business logic developers went full steam ahead sprinkling functionality goodness across the application. The refactoring team's core focus was solely on the infrastructure. A good analogy is that the engines were changed out on the airplane while it was still in flight.

Remember the Food Pyramid for Healthy Living
Symptom: Developers were interfering with each other
Root Cause: Poor code structure and no separation of concerns
Solution: Modularize the code base and adopt a layered architecture

Having a tightly coupled code base can wreak havoc on developer productivity. Interaction complexity is extremely high and one code change can potentially affect multiple areas of the code. The application can be classified as brittle and development typically takes much longer than in a layered architecture. Building layers in your architecture will lead to a well-defined separation of concerns allowing each layer to be understood as a coherent whole. Interfaces are typically used as the contract between each layer. By implementing the Interface pattern your application can now substitute each layer with an alternative implementation. The benefits of this paradigm will be discussed in greater detail in the next symptom. The first step taken to modularizing the code base was to break the project into five distinct sub-projects. These sub-projects were compiled according to their dependencies. This enforced that each layer only knew about the ones below it. Figure 1 offers a before and after picture of the logical code base.

Notice that each distinct functional area dictates where the layer boundaries are. These are not revolutionary concepts, just good pragmatic architectural decisions. A loosely coupled architecture makes adopting an Inversion of Control (IOC) container like Spring much easier than not. The next symptom's solution would be hard if not impossible to implement without a layered architecture.

Take Your Injections
Symptom: Unable to consistently run tests in a development environment or gather Web Services metrics.
Root Cause: Reliance on external Web Services availability
Solution: Dependency injection, aspect-oriented programming (AOP), and mock objects

With the advent of SOA came the troublesome deal of unit testing. As if testing wasn't hard enough, you're now reliant on someone else's application to execute unit tests. Another problem with a SOA is the inability to audit your service calls without intrusive metrics-gathering code throughout your application. The first order of business was to create an external provider layer with well-defined interfaces. Figure 2 shows the use of the façade pattern for all of the external Web Service calls done by the application. Classes in the external provider layer implement an interface and are injected into plain old Java object (POJO) business services.

One of the things that Spring excels at is being an IOC container. Spring uses a declarative approach to move an object's dependencies into an XML configuration file.

<!-- Spring wired Business Service bean -->
<bean id="businessServiceTarget" class="example.BusinessServiceImpl">
   <property name="provisionWebService">
   <ref bean="provisionWebService"/></property>
   <property name="orderDAO"><ref bean="orderDAO"/></property>
</bean>

This example shows how a POJO business service class would be defined in a Spring configuration file. This bean is injected with two other beans called provisionWebService and orderDAO. The injected provisionWebService bean implements the interface ProvisionIF. By having public getter() and setter() methods on our business service we can literally change the implementation of the provisionWebService attribute at runtime. As long as we inject another class that implements the ProvisionIF interface our business service bean will be content. This same pattern would apply to the order data access object (DAO) injected bean.

public class BusinessServiceImpl implements BusinessServiceIF {
   private ProvisionIF provisionWebService;
   public ProvisionIF getProvisionWebService();
   public void setProvisionWebService(ProvisionIF provisionWebService);

   private OrderDAOIF orderDAO;
   public OrderDAOIF getOrderDAO();
   public void setOrderDAO(OrderDAOIF orderDAO);

}

Unit testing our business service class just got a lot easier. Now we don't have to rely on the provisioning Web Service to be available for testing. We can use Spring to inject a stub class that implements the ProvisionIF interface and returns canned data. This class has to be a concrete implementation and physically present as a Java class. If we don't want a proliferation of stub classes we can always choose a mocking framework like EasyMock. EasyMock provides mock objects for interfaces and generates them on-the-fly using Java's dynamic proxy mechanism. Tools like dependency injection and mock objects are a perfect fit for TDD since application code should be written so that modules are testable in isolation. These two paradigms for testing are shown graphically in Figure 2.

We have unit-tested our code, but how do we capture live metrics data in a production environment without writing copious amounts of auditing logic? SOA has enabled distributed architectures, but with that comes the headache of uniformly capturing input/output data, storing error messages, measuring request/response times, and correlating message calls. AOP aids developers in handling cross-cutting concerns such as security, auditing, logging, and transaction management. An 'advice' in Spring is an action taken by the AOP framework at a particular point during the execution of a program. The most typical types of 'advice' used in Spring are "around," "before," "after," and "throws." Spring has modeled these types as interceptors that are done using a chaining mechanism. Spring lets you attach N number of interceptors to a wired bean. In the example in Listing 1 we've attached an "around" audit interceptor to our bean that performs actions before and after the method invocation. This interceptor is applied declaratively. Spring uses dynamic proxies for AOP proxies but can also use CGLIB proxies if it's necessary to proxy a class rather than an interface.

Our example shows our business service bean dynamically proxied with a custom audit interceptor and a Hibernate interceptor provided by Spring. The audit interceptor is just another Spring wired bean that implements a MethodInterceptor interface. The audit interceptor will insert audit information into an auditing database before and after our business service method is invoked. This auditing information is now an integral part of capturing application metrics and managers distribute reports on a daily basis. The Hibernate interceptor is a class provided by Spring that handles Hibernate session management.

It's Dozerin' Time
Symptom: Proliferation of Java Bean mapping classes
Root Cause: Need for transformation between internal do main objects to external Web Service calls
Solution: Creation of a framework for Java Bean mapping

A side effect of an SOA is the passing of domain objects between different systems. Typically, you won't want internal domain objects exposed externally and won't allow for external domain objects to bleed into your system. Creating a mapping framework is useful in a layered architecture where you're creating layers of abstraction by encapsulating changes to particular data objects versus propagating these objects to other layers (i.e., external service data objects, data transfer objects, or internal service data objects). Most programmers will develop some sort of mapping framework and spend countless hours and thousands of lines of code mapping to and from their many transfer objects.

One of the many positive side effects of the refactoring project was the birth of an Open Source project called dozer (http://dozer.sourceforge.net). Dozer was extracted from the refactored code base and made generic enough to handle most real-world mapping scenarios. Dozer supports Java Bean transformation, conversion, simple property mapping, complex type mapping, bidirectional mapping, and implicit/explicit mapping as well as recursive mapping.

Hibernate Through the Cold Winter Nights
Symptom: Scaling and performance issues
Root Cause: Persistence mechanism written with EJB entity beans
Solution: Data access layer with Hibernate and Spring integration

There have been many articles written about the reasons why we shouldn't use entity beans. Some of these reasons are performance issues, an immature query language, and the inability to unit-test and use entity beans as POJOs. Hibernate solves all of these issues and more. Before implementing Hibernate we created a technology-agnostic data access object (DAO) layer. This simply means that if there was an even newer, shinier persistence technology created tomorrow we could replace Hibernate with a minimal amount of refactoring. Some of the immediate benefits of using Hibernate are:

Spring provides integration with Hibernate for DAO implementation support, resource management, and transaction management. Spring provides a Hibernate interceptor class that manages the Hibernate session throughout a transaction by using Spring's AOP proxying. As a developer you just need to apply that interceptor to any of your Spring wired beans. Spring also lets you define a Hibernate session factory object. An example is presented in Listing 2.

Notice that the session factory is injected with a bean called dataSource. The dataSource bean can be injected with any class that implements the javax.sql.DataSource interface. Spring has the capability of loading different context files based on different testing scenarios. If you're running tests in an application container the dataSource bean can be defined as a container-based JNDI object.

<!-- In-Container implementation of a DataSource -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
   <property name="jndiName"><value>jndi_datasource</value></property>
</bean>

If you're running tests out of the container the dataSource bean can be a simple JDBC connection.

<!-- Out of container implementation of a DataSource -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.SingleConnectionDataSource">
<property name="driverClassName"><value>someDriverName</value></property>
<property name="url"><value>myUrl</value></property>
<property name="username"><value>user</value></property>
<property name="password"><value>password</value></property>
</bean>

By looking at this one example the power of dependency injection becomes truly apparent. One slight configuration change and we're one step closer to running our entire suite of unit tests without an application server.

It's Springtime, Get 'Outside' and Enjoy the Weather
Symptom: Code-level unit testing was taking about 10-15 minutes for one test
Root Cause: Code was completely dependent on the application server
Solution: Decoupling business logic from the EJBs allowed for out-of-container testing on every Java class

At this stage of the game the prognosis is getting better for our refactored application. We've successfully layered our architecture, applied dependency injection to move our object's dependencies into a configuration file, used AOP to audit our Web Service calls, used a Java Bean mapping framework, and replaced all the entity beans with Hibernate. One last step has to be implemented before our entire suite of unit tests can be tested out- of-container. Before Spring, EJBs were seen as the panacea for infrastructure details like security and transaction management. Most developers chose stateless session beans to handle all of their container-managed transactions. They also probably chose to store their business logic in these same beans, thus coupling them to the container with no chance of writing simple unit tests. Figure 3 offers a solution to this problem.

All of the EJB business logic has been moved into a Spring wired bean called BusinessService, which is a POJO. The EJB simply retrieves the bean out of the Spring context and calls business methods on it. In this particular example the BusinessService bean is injected with two other Spring wired beans. It's also wrapped with a custom audit interceptor as well as a Spring Hibernate interceptor. The EJB still manages the JTA transaction while the Hibernate interceptor does the Hibernate session management. The only reason that stateless EJBs weren't discarded altogether was for their RMI remoting capabilities. In future iterations we plan to remove the stateless EJBs altogether. Spring has transaction interceptors that can simulate the transactional work of a container-managed EJB.

Unit testing was made painless by using Spring's HibernateTransactionManager class. Using that class we were able to have our JUnit test cases simulate the transaction management and Hibernate session management capabilities performed in the container. Getting full transaction and Hibernate session support outside of the container lets your IDE mimic testing inside the container. Figure 4 graphically represents the unit-testing transformation.

You Reap What You Sow
At the end of the refactoring project many tangible results had been achieved:

On any refactoring project you really do reap what you sow. Refactoring has an incredible return on investment in the short- and long-term future of your application. A word of caution before using any 'new' technologies: create a prototype application of what you want your 'end-state' architecture to look like. This prototype should be the proving grounds for all of the gory technical details and answer many hypothetical questions at two in the morning. Without our prototype, this refactoring project would have failed.

Summary - Prognosis Good
Applications that choose frameworks that allow for loose coupling can start to shed some of their J2EE baggage and leave a lot of the plumbing to tools like Spring. The days of being tightly coupled to your application server for unit testing are quickly slipping away. By using a pragmatic approach to Open Source tool selection many rewards will be instantly achieved. Applications will be more flexible and developer productivity and efficiency should increase dramatically. Not only do these tools help in decoupling, but they also tend to make applications a lot simpler by removing dependencies on J2EE containers. After having the pleasure of working on a loosely coupled flexible architecture you will wonder how any work got done before.

References

© 2008 SYS-CON Media Inc.