Most read:
Popular archives:
Java Q&A Forums - Let the great migration begin
We're pleased to announce the first phase of the integration of the Java Q&A Forums with our community platform, JavaWorld's
Daily Brew. Whether you're one of our longtime forum users or a brand newbie, we hope you'll visit the Java Q&A Forums in their new home alongside JW Blogs.
| Enterprise AJAX - Transcend the Hype |
| Oracle Compatibility Developer's Guide |
Page 2 of 5
Currently, OOP serves as the methodology of choice for most new software development projects. Indeed, OOP has shown its strength when it comes to modeling common behavior. However, as we will see shortly, and as you may have already experienced, OOP does not adequately address behaviors that span over many -- often unrelated -- modules. In contrast, AOP methodology fills this void. AOP quite possibly represents the next big step in the evolution of programming methodologies.
We can view a complex software system as a combined implementation of multiple concerns. A typical system may consist of several kinds of concerns, including business logic, performance, data persistence, logging and debugging, authentication, security, multithread safety, error checking, and so on. You'll also encounter development-process concerns, such as comprehensibility, maintainability, traceability, and evolution ease . Figure 1 illustrates a system as a set of concerns implemented by various modules.

Figure 1. Implementation modules as a set of concerns
Figure 2 presents a set of requirements as a light beam passing through a prism. We pass a requirements light beam through a concern-identifier prism, which separates each concern. The same view also extends towards development-process concerns.

Figure 2. Concern decomposition: The prism analogy
A developer creates a system as a response to multiple requirements. We can broadly classify these requirements as core module-level requirements and system-level requirements. Many system-level requirements tend to be orthogonal (mutually independent) to each other and to the module-level requirements. System-level requirements also tend to crosscut many core modules. For example, a typical enterprise application comprises crosscutting concerns such as authentication, logging, resource pooling, administration, performance, and storage management. Each crosscuts several subsystems. For example, a storage-management concern affects every stateful business object.
Let's consider a simple, but more concrete, example. Consider a skeleton implementation of a class encapsulating some business logic:
public class SomeBusinessClass extends OtherBusinessClass {
// Core data members
// Other data members: Log stream, data-consistency flag
// Override methods in the base class
public void performSomeOperation(OperationInformation info) {
// Ensure authentication
// Ensure info satisfies contracts
// Lock the object to ensure data-consistency in case other
// threads access it
// Ensure the cache is up to date
// Log the start of operation
// ==== Perform the core operation ====
// Log the completion of operation
// Unlock the object
}
// More operations similar to above
public void save(PersitanceStorage ps) {
}
public void load(PersitanceStorage ps) {
}
}
In the code above, we must consider at least three issues. First, other data members do not belong to this class's core concern. Second, implementation of performSomeOperation() seems to do more than perform the core operation; it seems to handle the peripheral logging, authentication, multithread safety, contract validation, and cache management
concerns. In addition, many of these peripheral concerns would likewise apply to other classes. Third, it is not clear if
save() and load() performing persistence management should form the core part of the class.
Although crosscutting concerns span over many modules, current implementation techniques tend to implement these requirements using one-dimensional methodologies, forcing implementation mapping for the requirements along a single dimension. That single dimension tends to be the core module-level implementation. The remaining requirements are tagged along this dominant dimension. In other words, the requirement space is an n-dimensional space, whereas the implementation space is one-dimensional. Such a mismatch results in an awkward requirements-to-implementation map.
A few symptoms can indicate a problematic implementation of crosscutting concerns using current methodologies. We can broadly classify those symptoms into two categories:
Combined, code tangling and code scattering affect software design and developments in many ways:
Since most systems include crosscutting concerns, it's no surprise that a few techniques have emerged to modularize their implementation. Such techniques include mix-in classes, design patterns, and domain-specific solutions.
With mix-in classes, for example, you can defer a concern's final implementation. The primary class contains a mix-in class instance and allows the system's other parts to set that instance. For example, in a credit card processing example, the class implementing business logic composes a logger mix-in. Another part of the system could set this logger to get the appropriate logging type. For example, the logger could be set to log using a filesystem or messaging middleware. Although the nature of logging is now deferred, the composer nevertheless contains code to invoke logging operations at all log points and controls the logging information.
Behavioral design patterns, like Visitor and Template Method, let you defer implementation. However, just as in case with mix-ins, the control of the operation -- invoking visiting logic or invoking template methods -- stays with the main classes.
Domain-specific solutions, such as frameworks and application servers, let developers address some crosscutting concerns in a modularized way. The Enterprise JavaBeans (EJB) architecture, for example, addresses crosscutting concerns such as security, administration, performance, and container-managed persistence. Bean developers focus on the business logic, while the deployment developers focus on deployment issues, such as bean-data mapping to a database. The bean developer remains, for the most part, oblivious to the storage issues. In this case, you implement the crosscutting concern of persistence using an XML-based mapping descriptor.
The domain-specific solution offers a specialized mechanism for solving the specific problem. As a downside to domain-specific solutions, developers must learn new techniques for each such solution. Further, because these solutions are domain specific, the crosscutting concerns not directly addressed require an ad hoc response.
Good system architecture considers present and future requirements to avoid a patchy-looking implementation. Therein lies a problem, however. Predicting the future is a difficult task. If you miss future crosscutting requirements, you'll need to change, or possibly reimplement, many parts of the system. On the other hand, focusing too much on low-probability requirements can lead to an overdesigned, confusing, bloated system. Thus a dilemma for system architects: How much design is too much? Should I lean towards underdesign or overdesign?
For example, should an architect include a logging mechanism in a system that does not initially need it? If so, where should the logging points be, and what information should be logged? A similar dilemma occurs for optimization-related requirements -- with performance, we seldom know the bottlenecks in advance. The usual approach is to build the system, profile it, and retrofit it with optimization to improve the performance. This approach requires potentially changing many system parts indicated by profiling. Further, over time, new bottlenecks may appear due to changed usage patterns. The reusable library architect's task is even more difficult because he finds it harder to imagine all the usage scenarios for the library.