|
Evolving J2EE specifications continue to provide developers and architects with added performance muscle and flexibility for building and optimizing enterprise applications. J2EE advances are both a boon and a challenge as they can provide additional capabilities while also requiring refined development skills. J2EE applications incorporate complex distributed logic and, hence, demand careful, intelligent, and innovative design and implementation techniques. I'll outline various programming design techniques that empower J2EE developers to create more robust enterprise applications.
A J2EE application is comprised of multiple artifacts, known as Java components, which act like musicians in an orchestra. Each J2EE artifact is required to perform its role, while working in concert with the other components to create the complete J2EE symphony. Developers and architects serve as composers, with the task of ensuring that each component "instrument" plays its assigned notes - without causing performance discord by consuming too many memory resources or failing to deliver its artifact "notes" on queue. The large number of instruments in a J2EE symphony makes it difficult for developers and architects to identify the source of out-of-tune components. J2EE architects and developers, therefore, play a vital role in aligning individual artifacts into powerful and scalable J2EE applications.
The adoption of proven design and implementation techniques can help ensure that J2EE applications are flexible, scalable, and can be used as templates for new projects. The following sections outline best practices that every developer or architect "composer" should use to support successful J2EE implementation.
Choose the Best-Fit Application Architecture There's no single application architecture that addresses all organizational goals. As such, organizations must use business objectives and end-user requirements as foundation footings for their application infrastructures. An architecture should enable the application to scale gracefully and have the performance to virtually eliminate Mean Time Between Failures (MTBF) and Mean Time To Recovery (MTTR). The architecture should also take advantage of object-oriented (OO) design patterns and principles, which support object scope extensibility and promote reusability, portability, and parsimonious use of computing resources.
Perhaps the biggest question posed by J2EE implementations is the use (or misuse) of Enterprise JavaBeans (EJBs). EJBs constitute a major and complex component of the J2EE specifications and remain a focal point for application server vendors. Many distributed Web applications maintain the state and session information of their clients and connections. It's essential that organizations select a data access methodology - such as direct Java Database Connectivity, Data Access Objects (DAO), or Java Data Objects (JDO) - that addresses both application user requirements and overarching business goals.
In addition to taking full control of the session life cycle, application architectures should include proven procedures for storing, restoring, recovering, and releasing state information. These decisions should be independent of application deployment patterns such as cloning, clustering, and split Java Virtual Machines (JVMs). For example, while state information can be stored either at the Web tiers or at the EJB tier, the application architecture should target the Web container to store the state information.
Enterprise design patterns should be the defining yardsticks for application architectures. Modeling and implementing business objects using design patterns creates a productive development environment that is built on reuse and harvests proven knowledge and assets. The J2EE specification necessitates intricate handling of checked and unchecked exceptions. Application architectures, therefore, should enable developers to identify and isolate exceptions, present a meaningful message to the presentation layer, and provide a straightforward recovery path to the business tier.
Prevent Objects From Flooding the JVM The adage "a house is only as strong as its foundation," can also be used to underscore the role of JVMs as the bedrock of a J2EE application. JVMs are arguably the foundations for applications; J2EE components are built and reside in JVMs. The majority of J2EE application performance problems are caused by JVM heap allocations failures, frequent garbage collections, and out-of-memory situations.
Developers can sidestep the majority of J2EE application performance challenges by using an application architecture that judiciously creates new objects, emphasizes object reuse, and extends application relevance through additional responsibility (behavior) and a wider scope in the domain. This approach significantly enhances JVM performance by maintaining smaller heap sizes and fewer object allocations, which in turn minimize garbage collections and eliminate out-of-memory situations that cause application performance problems.
The developer community is largely unaware of Java's robust heap management and garbage control capabilities. The java.lang.ref package, for example, classifies object references into active, passive, convenient, and dead references. The package also enables developers to manipulate objects through event queues and listeners. The java.lang.ref enables business objects to be wrapped in different reference types, which gives the JVM greater control over object life spans. Java.lang.ref requires a deep understanding of the garbage collection process. Once Java.lang.ref is understood and used, however, developers can create code that optimizes run-time memory utilization.
Java Development Skills That Complement J2EE Design Skills J2EE architectural excellence without programmatic prudence is an exercise in futility. Many projects do not realize their potential because elegantly crafted Java code is compromised by poor application design. Similarly, many applications have failed to deliver desired performance because developers strayed from the rigid coding disciplines required to address the complexities of J2EE application program interfaces (APIs). A typical J2EE application contains distributed logic, which requires a balanced approach to address myriad variables, including algorithms, concurrency, isolation, transaction management, and inter-tier communication. Code development for APIs, in turn, must address resource conservation, include sound exception-handling features, and allow graceful application recovery.
Some major Java coding challenges that developers face, include implementations that fire multiple-threads, hard coding of transactions, programmatic control of security roles and persistence, tight coupling objects between tiers, and caching huge amounts of information for each client thread. Many developers also add needless strain to their Java environments because they create redundant features in J2EE applications that are already available in J2EE API and application server environments. Many developers, for example, include caching features in J2EE applications that are already available in the underlying J2EE application servers. A thorough understanding of J2EE specifications and APIs, as well as a sound awareness of the extended features provided by the application servers, will yield higher productivity with quality and optimized resources. Good J2EE architecture and developer craftsmen will continually research the best techniques and methodologies for future application initiatives.
Selectively Implement OO Design Principles Contrary to common belief, prolific use of OO techniques does not necessarily make J2EE applications more robust. Deep inheritance hierarchies, in which each class has a large number of ancestors, can often be counterproductive because they expand the JVM object stack and require increased overhead in object creations. J2EE containers rely heavily on object serialization for session persistence management; serialization of such objects with deep inheritance is a strain on the application server.
Object hierarchy adds overhead unless carefully handled through techniques such as declaring class variables as transient and implementing externizable interfaces. Flattening an object hierarchy requires a balance of performance and maintainability during development. Nevertheless, a flatter object hierarchy minimizes object allocation and collection, which supports optimal performance. Programming to interfaces - not classes - and implementing fa?ade patterns enables loose object coupling, adds more flexibility, allows for coarse-grained information exchange, and decreases network latency. Developers reduce design complexity and optimize run-time garbage collection by declaring class constructs, such as instance variables and method arguments, to the least specific type (i.e., the highest ancestors in the hierarchy).
Gang of Four (GoF) patterns, particularly strategy and state design patterns, favor object compositions over class (concrete) inheritance. This is a particularly suitable alternative to class inheritance because it provides developers with the added flexibility to alter an object's behavior during run time through delegation. An additional way of achieving flexibility is programming to interfaces rather than to classes. Interface-based architectures add strength and scalability to J2EE applications because of their simplicity and loose coupling between objects.
An application architecture that minimizes object allocations, increases the life and scope of objects, and supports the effective reuse of objects through reinitialization and pooling will perform well. Multi-threading, for example, can be avoided by using asynchronously concurrent Message-Driven Beans (MDBs), which demand less from the JVM, avoid complexity, and reduce object count. Finally, synchronization of code blocks should be justified and avoided as much as possible.
J2EE software development is a complex, multifaceted process, much like the business cases it solves. The four main points of this article are intended to serve as seeds for discussion and debate among J2EE development teams when determining best practices for architectural and development strategies. It's the responsibility of architects and developers to harness the potential of J2EE for their organizational objectives.
References
Stearns, Beth, et al. (2002). Designing Enterprise Applications with the J2EE Platform, Second Edition. Addison-Wesley.
Johnson, R. (2003). Expert One-on-One J2EE Design and Development. John Wiley & Sons, Inc.
Crupi, John, et al. (2001). Core J2EE Patterns: Best Practices and Design Practices. Prentice Hall.
|