Friday, March 03, 2006

Elements of Web Application Architecture

This article consists of a few in-progress notes I assembled in preparation for a project meeting today about application architecture. While still rough, they outline and elaborate upon a number of design elements that I think can contribute to a successful and responsive web application architecture. As usual, I'm heavily indebted to the writings of Rod Johnson and Martin Fowler.

1. A strong, domain-driven and object-oriented model
Domain models represent the core, object-oriented building blocks on which an application architecture is structured. Model objects represent fine-grained objects, often mapped to database tables, which contain the essential data and behaviours of your application domain. In the past, the status quo of J2EE architecture has always dicated that model objects should serve as simple data holders instead of encapsulators of behaviour. Why have rich, behaviour-driven data models been so consistently overlooked design in J2EE architectures? As I see it, there are a couple of legacy designs which served to limit the way in which data objects can be designed:

a) EJB entity beans
Due to constraints in the EJB approach, entity beans needed to be just data holders rather than full-fledged objects responsible both for encapsulating data and providing business logic for working with that data. This constraint forced developers to inappropriately push domain logic up into the service tier as session beans. The problem with this design is that it blurs a critical distinction between fine-grained business logic (which rightfully belongs in the model) and coarse-grained application logic suitable for the service tier.

b) Hand-rolled DAOs and Direct use of JDBC
The use of direct SQL calls and ad-hoc data access objects (DAOs) tend to lead towards an architecture that emphasizes the underlying structure of the database, causing data objects to be less like real-world objects. The overhead of manually maintaining the kind of object graphs required by real business logic is often too complex for ad-hoc DAO objects, thus leading back to this problem of model objects being misused as simple data holders.

2. A clearly defined and widely useable service layer containing higher-level application functionality
The service layer packages up broader application functionality, consisting of logic that depends on the composition of several domain objects. This layer generally contains the code that does the real work of the application, suitable for use by clients in the presentation layer.

3. A razor-thin presentation layer
To satisfy the goal of creating reusable services, the servlet tier needs to be thinned out considerably, and its responsibility should be reduced to three main tasks: a) parsing and structuring user input; b) calling into the service tier; and c) packaging up output to the user. The workflow for both of these tasks is further facilitated by a web MVC framework.

4. A consistent controller framework to manage the view
In the current J2EE web tier landscape, there are two main types of technology available for designing a well-separated view and controller layer, distinguished largely by the level at which they model user interaction:

a) Request/Response-driven MVC Frameworks
Examples: Struts, Spring MVC, WebWork

This type of tool closely models the servlet lifecycle, usually providing a fairly thin controller layer used to pull values from the user, process commands and forms, and invoke data validation. The controller layer then passes off the resulting data to the view tier--implemented as templates using JSP or Velocity, for example--for rendering.

b) Page- or Event-driven Frameworks
Examples: JavaServer Faces, Tapestry, Wicket

This type of framework attempts to model the workflow and structure of reuseable page components as real objects. The goal is to facilitate the composition and reuse of user interface widgets in a similar manner to many desktop UI toolkits such as Swing. These tools tend to abstract the processing of request/responses in terms of higher-levels events.

5. Clear separation of dependencies between application tiers
Preserving the separation between tiers of the application can be facilitated with a combination of design techniques and the use of an application framework and inversion of control container such as the Spring Framework. From a general perspective, there are a few concrete things that can be done to improve loose coupling of objects:

a) Separate your concerns: each object should be clearly focussed on the details particular to its job. It shouldn't be encumbered by logic or API that extends outside its domain.
b) Reduce overall dependencies of business logic on non-core APIs (including Servlets and JAXB)
c) Inject dependencies rather than hard coding them: don't wire up services or do lookups in code, let a framework help you out so that your code can easily be used in other contexts.

6. Testability
Avoid tightly coupling business logic and services to third-party APIs and to themselves. If you can't test your application using a diverse set of testing techniques--many of which require working with isolated units of behaviour--because of architectural constraints, your application won't work. Period.

The End Goal: Flexibility and Resilience
In this context, when I use the word "flexible" I don't mean as a synonym for "power" or "control," but rather in a literal sense: Successful architectures need to be able to bend and flex to accommodate unforseen changes. Constrast this idea with the notion of architectural resilience, which involves a fundamental consistency and vision that remains coherent and useful despite constant change. Both are essential for a successful architecture.