Architecture

Application Domains

The following application domains will be discussed in turn:

  • Presentation Services
  • Security
  • Data Access
  • Business Services
  • Transaction Management
  • Performance

Presentation Services

Overview

The look and feel of the application is separated out to provide common user interaction framework using Struts and other open source libraries.

Design Principles

  • Adhere to standards and generally accepted best practices to enable deployment on alternate Java EE containers and databases
  • Extensibility
  • Allow for scalability and performance, e.g. minimise server state.
  • Provide easy and intuitive navigation as guided by the Information Architecture
  • Enable multilingual support
  • Support wide range of modern browsers

Security

The application design will enable authentication, authorisation, and encryption to be plugged in.

Architecture (with Flex Mappings)
Architecture (with Flex mappings)

Data Access Layer

Object Persistence

The Hibernate framework will be used to persist objects to a relational database. The test environment will use HSQLDB; however, the application will support multiple relational databases configured from a properties file.

Hibernate maps objects and object associations to a database schema and performs low level JDBC tasks.

Plain old Java objects (POJO) are used to model data using Object Oriented design principles. The persistent objects must conform to the Java Bean specification, which amongst a few other rules, require that get, set methods are provided for each data attribute. Hibernate provides various mechanisms for saving, updating and retrieving object state to/from permanent storage, including a flexible query language, standard SQL, and a strongly typed query API.

The mapping of objects to tables is defined in XML configuration files. We will use the XDoclet framework to automatically generate the mapping files based on meta data included in the java source files of the persistent objects using JavaDoc-like annotations.

Domain Object Model

Core application objects, which pertain to the business problem, are referred to as domain objects. The Domain Object Model is the core object-oriented representation of the business application.

The domain objects will be developed as Java objects, conforming to the Java Bean specification, within the package:
<domain>.<application-name>.model

A subset of the domain objects will require persistence. These will have XDoclet annotations to define the essential Hibernate mapping and configuration information; e.g.
/** * @hibernate.class * table="USERS" */ public class User extends BaseObject { private String username; /** * @hibernate.property * column="USER_NAME" */ public String getUsername() { return userName; } public void setUsername(String username) { this.username = username; } }

Persistent objects must implement java.io.Serializable. A base super class will be provided to provide any common functionality and which implements the Serializable interface.

Data Access Objects

A Data Access Object will be provided for each primary business entity. Primary business entities may have dependent Attribute entities such as the Address entity in this application, whose lifecycle is controlled by the primary business entity. The data access logic of Attribute entities may be included in the DAO of the primary entity.

Associative entities (entities that resolve many-to-many relations between primary entities) which do not have their attributes do not need a corresponding object - Hibernate will create these associative tables automatically.

Each DAO will have a DAO interface and should typically as a minimum provide the following methods; e.g.
public interface UserDAO { public List findAllUsers(); public List searchUser(User userCriteria); public User loadUser(Integer userId); public void storeUser(User user); public void removeUser(User user); }

The DAO interfaces will be placed within the package:
<domain>.<application-name>.dao

The DAO may provide similar methods for the attributive and associative entities which "hang off" the primary entity.

This allows alternative concrete implementations of the DAO without affecting dependent code. For this project, Hibernate implementations of all the DAO interfaces will be developed. These will be placed within the package:
<domain>.<application-name>.dao.hibernate

A base class provided by the Spring framework is used to abstract away some of the low level details normally associated with using Hibernate, such as creating a Hibernate session, opening a transaction, committing the transaction, and closing the session. For example:
public class UserDAOHibernate extends HibernateDaoSupport implements UserDAO { public List findAllUsers() { return getHibernateTemplate().loadAll(User.class); } public User searchUser(Integer userId) { return (User) getHibernateTemplate().load(User.class, userId); } public void storeUser(User user) { getHibernateTemplate().saveOrUpdate(user); } public void removeUser(User user) { getHibernateTemplate().delete(user); } }

  • The store method will either insert or update the record into the database depending on the state of the id field as specified by the 'unsaved-value' in the id mapping annotations. For example, if 'unsaved-value' is set to null, then a null value for the id field will result in the object being inserted into the database.

For queries which require access to the Hibernate Session, it is best to implement a Hibernate Callback object; e.g.
public List searchUser(final User userCriteria) { return getHibernateTemplate().executeFind(new HibernateCallback() { public Object doInHibernate(Session session) { Example example =

Spring Hibernate template methods throw an unchecked exception - DataAccessException. The getCause() method of this class can be used to access the underlying JDBC exceptions.

Exception Handling

It is recommended to throw unchecked exceptions (exception classes which derive from RuntimeException) where the dependent code cannot be expected to recover from it. Where the dependent code can take an alternative course of action, then it is recommended to convert the exception into a checked exception (exception classes which derive from Exception). This way the programmer is forced to consider how the application might recover to improve the usability and resilience of the application. Wherever possible, existing exception classes which adequately describe the cause of the problem should be used; writing your own only when a suitable class does not already exist.

Business Services

Business services have the following responsibilities:

  • Act as a facade to the data access layer. A business service method should correspond to a Use Case, encapsulating all the business and data access logic necessary to satisfy a complete user goal. A business service method may reference multiple DAOs and access the database multiple times in order to fulfil a service request.
  • Demarcate a business transaction. It is common for data access occurring within a service method to be performed within a single transaction, such that if there is an exception, the state of the application is rolled back to the beginning of the service invocation.
  • Manage workflow. A new workflow may be initiated by a service method. The method should be responsible for the execution of the entire workflow. Business services may be federated, with higher level services decomposed into more fine grained services.
  • Provide views of data, agnostic to a particular implementation of the presentation layer.

Typically, a Business Service will be developed for each subsystem/module, such as UserManager, and ContactManager. One Business Service may use multiple DAOs.

Like DAOs, Business Services will include a Java interface, which defines the contract which will be maintained as the application evolves over time. New methods may be added, but existing methods should continue to be supported.

The service interfaces along with their concrete implementations will be placed in the package:
<domain>.<application-name>.service

An example of a Business Service interface is:
public interface UserManager { public User authenticate(String userName, String password) throws ServiceException; public void updateUser(User user, Integer[] roleIds); throws ServiceException; }

The same rule of thumb for exception handling should be followed: throw an unchecked exception (which does not need a throws clause nor must be caught) for error conditions from which the dependent code cannot be expected recover, mainly for reasons of ease of use; and throw a checked exception, such as ServiceException, where the dependent code could perhaps try again or provide a graceful exit. Dependent code can still catch unchecked exceptions should it wish.

Business Service implementations coordinate the use of DAOs, apply business rules, and perform any business processing of data. In some cases, business service may simply mirror DAO methods. However, DAOs should only be accessed through the Service layer to accommodate any future change which may require the DAO to participate in workflow logic or some other pre- or post-processing.

Managing Dependencies between Layers

The Spring framework will be used to manage the dependencies between layers using the design principle of Inversion of Control (IoC).

A well designed application has object dependencies which flow one way: typically from the presentation layer through to the data access layer. For example, a service object needs a reference to a data access object. The strategies for creating these references include:

Strategy Disadvantages
Create the reference in the dependent code using the new operator. If the implementation of the referenced object changes, such as changing the object persistence technology, then all the code which reference this object must be updated.
The implementation cannot be replaced with a stub/mock object for testing.
Use an object registry such as a JNDI server. The dependent code must run within a container which provides, e.g., a JNDI service, which means that it cannot be run outside of the container for testing.

An IoC container such as Spring will "inject" the object reference into the dependent object at runtime using either a setter method or public constructor; e.g.
public class UserManagerImpl implements UserManager { private UserDAO dao; public void setUserDAO(UserDAO dao) { this.dao = dao; } public User getUser(Integer userId) throws ServiceException { try { return dao.getUser(userId); } catch (DataAccessException e) { throw new ServiceException(e.getMessage(), e); } } . . . }

When an object reference is obtained from the IoC container; e.g.
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( new String[] { "applicationContext-hibernate.xml", "applicationContext-service.xml", "applicationContext-database.xml" }); userService = (UserManager) ctx.getBean("userManager");

All the other objects required by the instantiated object are also instantiated and populated using the relevant setter methods or as arguments in a constructor method. (We have used the setter method in this application.) The may involved the referenced objects to be likewise injected with their required dependent objects, and so on until the requested object graph is fully loaded.

Object dependencies are defined in an XML configuration file; e.g.
<bean id="userManagerTarget" class="<domain>.<application-name>.service.UserManagerImpl"> <property name="userDAO" ref="userDAO"/> </bean>

The advantage of this approach, in addition to keeping code clean and light, is that the code can also run outside of a container by manually setting the properties. This is useful for testing purposes; a mock DAO for example can be set in the service object.

Transaction Management

Transactions are important to ensure that units of work are performed in an ACID manner. ACID standards for:

  • Atomic
  • Consistent
  • Isolated
  • Durable

An example of a transaction is creating an Order. This process might involve processing the payment and creating a new Order for fulfilment, which in turn involves interacting with two back office resources: the credit card payment service and the database. An exception condition in one should cause the undoing of the whole process. You do not want to take payment without creating an Order for fulfilment, and you do not want to start order fulfilment without confirming payment. A simplified example follows:

Transaction Bounds:
public class OrderManagerImpl { // demarcation of transaction public void checkout() { paymentDAO.processPayment(); orderDAO.createOrder(); } } public class PaymentDAOImpl { public void processPayment() { // some data access code } } public class OrderDAOHibernate { public void createOrder() { // some data access code } }

Transactions are managed at the business service layer and are demarcated at the boundary of a business service method. The data access layer will participate in transactions but should not drive them, enabling separate data access methods to be used in the one transaction.

The Spring framework provides the coordinating infrastructure which enables the business service objects to demarcate abstract transactions without concern of the underlying resources involved, and data access objects to fetch transactional resources without concern about participation in transactions.

In addition, Spring provides declarative transaction demarcation using Aspect Oriented Programming (AOP) techniques. Transactions are declared in the service bean configuration file: applicationContext-service.xml in the <domain>.<application-name>.service package.

In this model, the business service object does not need to be transaction-aware at all.

A Spring application-context definition needs to contain an AOP transaction definition of the following form. The business service object is now proxied: every invocation goes through the transaction interceptor first, continuing with the actual service object, and going through the interceptor again on the way back. The service object implementation itself is defined as 'target', which is simply a naming convention to identify its role.
<!-- Order Manager: implementation --> <bean id="orderManagerTarget" class="<domain>.<application-name>.service.OrderManagerImpl"> <property name="orderDAO" ref="orderDAO"/> <property name="productDAO" ref="productDAO"/> </bean> <!-- Transaction declarations for business services. --> <bean id="orderManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager"/> <property name="target" ref-local="orderManagerTarget"/> <property name="transactionAttributes"> <props> <prop key="add*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="remove*">PROPAGATION_REQUIRED</prop> <prop key="*">PROPAGATION_REQUIRED,readOnly</prop> </props> </property> </bean>

The default exception behaviour is to roll back on runtime exceptions as in EJB. The Transaction Interceptor can also be instructed to roll back on a specific checked exception. Such rollback rules can be defined by strings appended to the transaction attribute:
"ExceptionNameToRollbackOn" or "ExceptionNameToCommitOn".

The following transaction attributes are available (shared with EJB and .NET):

Propagation Behaviour How to deal with the creation of new transactions and propagation of existing transactions:
required execute within a current transaction, create a new transaction if none exists
supports execute within a current transaction, execute non-transactionally if none exists
mandatory execute within a current transaction, throw an exception if none exists
requires new create a new transaction, suspending the current transaction if one exists
not supported execute non-transactionally, suspending the current transaction if one exists
never execute non-transactionally, throw an exception if a transaction exists

The default is required, which is typically the most appropriate.

Read-only: A hint whether to optimise for a read-only transaction. A value of true will just be interpreted by certain resources that can actually perform such optimisations. For example, a Hibernate Session does not try to detect and flush changes in a read-only transaction - a desirable optimisation.

Spring uses JTA (Java Transaction API) as the underlying Transaction Manager.