© 2002 International Business Machines Corporation. All rights reserved.
The packaging of J2EE applications is defined by the J2EE 1.2 specification. The intent of the spec is that Enterprise ARchive (EAR) files, the fundamental constructs that contain applications, be completely self-contained. Applications are expected to use APIs provided by the J2EE server vendor, or those within the EAR, and nothing else. This way, applications can be independent of each other. This, in fact, is stated quite clearly in section 8.1.2 of the specification:
A J2EE application consists of one or more J2EE modules and one J2EE application deployment descriptor. A J2EE application is packaged using the Java Archive (JAR) file format into a file with an .ear (Enterprise ARchive) filename extension. A minimal J2EE application package will only contain J2EE modules and the application deployment descriptor. A J2EE application package may also include libraries referenced by J2EE modules, help files and documentation to aid the deployer, etc.
The deployment of a portable J2EE application should not depend on any entities that may be contained in the package other than those defined by this specification. Deployment of a portable J2EE application must be possible using only the application deployment descriptor and the J2EE modules (and their dependent libraries) and descriptors listed in it.
This does not address the issue of the sharing of common code, or what are often referred to as dependent libraries. Essentially, these common libraries, which may be developed internally or purchased from external vendors, provide common function to multiple applications at run time. Creating common code is extremely valuable and provides a number of benefits that have been widely discussed elsewhere. The question here is how should these common code libraries be packaged at run time:
Should the common code binaries be shared at run time by applications the way operating systems or product libraries are shared?
Should each application just have its own local copy?
The generally accepted practice, and the one consistently recommended as described above and implied by the articles in the References section below, is that applications should be as self-contained as is reasonable. Thus, common code should be copied into each EAR. More precisely, common code used by applications should be placed in the root of the EAR (or an appropriate subdirectory) and shared across the entire EAR. See Footnote 1
In theory, code can also be placed within each WAR in WEB-INF/lib or within each EJB JAR, but code placed in either of these places is not normally sharable by other modules in the EAR. Since EARs are self-contained applications, it is generally best to place code libraries in the EAR root, EJBs in the EJB JARs, and servlets in the WAR.See Footnote 2
It is important to note that the benefits of reuse are independent of this issue. Whether the code is shared at run time or not, applications can still use a common set of code libraries that are developed elsewhere and still provide the usual benefits of reuse. This issue is about runtime packaging, not reuse.
Some organizations prefer to share common code libraries at run time for various reasons. In recognition of this fact, most J2EE server vendors have added non-portable extensions to support shared code libraries. In the case of IBM© WebSphere© Application Server Version 4.0, this is known as the lib/app classpath.See Footnote 3 Basically, there is a directory on each WebSphere Application Server node that can contain JAR files. WebSphere Application Server will automatically make those JARs visible to every application on the node.
At first glance, this would seem to suggest that common code should be placed on this common classpath. However, this is not the case. Common code can be placed here, but careful consideration of the issues is required since the implications boil down to a risk tradeoff between two opposing forces: the desire to make the common code runtime sharable, and the need to allow for application independence.
The benefits of placing common code in a common location and thus sharing it across all applications are:
- The common code development team only has to support one (or possibly two) versions in production, rather than the multiple versions that could conceivably exist if applications were bundled with common code versions. However, limiting the versions that applications can use could contain this situation.
- The common code and underlying databases can change in sync. Since the common code changes in all applications at once, it is easier to force database changes.
- Fixes to the common code automatically apply to all applications. Of course, this is mixed blessing.
- Some small amount of disk and memory space is saved by not having multiple copies of the same binary images on disk and in memory. Except for extraordinarily large libraries, this is not a significant benefit.
The challenges and risks raised by sharing the code across applications are:
- Backward compatibility is paramount. Since every application uses the same version of the common API, it is nearly impossible to coordinate API changes. Thus, the API must be very stable.
- Changes desired by one application impact all applications. This makes introducing change very difficult. Application teams will have to coordinate their development and test cycles.
- Fixes required by one application impact all applications. This creates the difficult tradeoff between fixing one application quickly and the risks introduced by the change in other working applications.
- Code on the lib/ext classpath is not reloadable. Thus, changes to it require restarts of the application servers. Updates to EARs require only a restart of the EAR.
The risks involved with code sharing are not insignificant, and are difficult to contain or even predict. This is why sharing is generally not recommended, except for extremely stable code (like JDBC drivers). Consider two scenarios:
mission critical applications (A, B, and C) are running on the
same node sharing some common code. One of the applications
fails in production. The appropriate teams are assembled and
it is quickly determined that there is a bug in the common
code that only affects application A. Application A requests
an immediate fix and the common code team quickly provides it.
Because of the critical nature of the defect, this code does
not go through a complete development and regression test
cycle. Application A wants the patch applied to production so
the application can be started (it has now been down for 2
days). Applications B and C do not want the patch applied
because they are afraid this may introduce an unexpected
problem. Do you:
- Apply the change and tradeoff the risk to applications B & C against the need to get application A back online? What if the patch fails and B then stops working? What if it turns out that the fix for A breaks B and that there is no easy way to resolve the conflict?
- Do you delay applying the fix by running the patch with applications A, B, and C is in a pre-production environment for 2 days and a full regression test suite? Do you have such an environment? What about the fact that application A is still down? What if the fix breaks application B but works for application A?
- Three mission critical applications (A, B, and C)
are running on the same node sharing some common code.
Application A has a major release scheduled for October and
would like to take advantage of some new function (major
changes) in the common code. Application B is not scheduled
for a release until next year and application C is currently
in maintenance with no development budget. What do you do?
- You could synchronize the development schedules for the two applications that are being actively developed. Of course, this doesn't address application C.
- You could update the common code in pre-production and fully retest all three applications. Who will pay the cost of retesting application C? What do you do if the new common code causes problems in application B or C? Application B may not wish to update their code until the next major release. Application C has no budget at all.
- You could update the package in production and pray.
All of the risks raised by these scenarios are alleviated by deploying applications as self contained EARs. Each application can deploy with different versions of the same common libraries without fear of disrupting other production systems. Self-contained EARs greatly simplify the deployment and operational procedures and reduce the need for synchronizing development cycles and test cycles of multiple, possibly unrelated, applications. The cost is minor, comprised mainly of slightly increased memory and disk footprints. Thus, in most situations, developing self-contained EARs is clearly the best choice.
Mapping applications to application servers
We have focused on the packaging issues surrounding applications and how best to create self-contained applications by leveraging the J2EE EAR concept. We have not discussed issues surrounding the sharing of application servers in a production environment. There are a variety of considerations surrounding whether multiple applications should share one application server (a single JVM) or whether each application should have its own application server. In general, the latter is easier to manage, although it does use more resources. The key message of this article is that by using the packaging approach recommended here, each application is self-contained. Therefore, multiple EARs can be deployed to the same application server or to different application servers without regard to classloader issues. The number of application servers then becomes an operational issue that should have little impact on the application development teams. This helps to reinforce the separation of concerns encouraged by J2EE.
It is worth noting that this problem is really just another example of the more general issues associated with sharing infrastructure. These same issues occur in the context of sharing WebSphere Application Server infrastructure, databases, and hardware. In many cases, it makes business sense to share infrastructure when the cost benefits justify the increased risk. In the case of shared infrastructure products and hardware, the potential cost efficiencies are substantial because of the enormous cost of maintaining computer hardware, and of buying and supporting products. Even then, many business units within large enterprises choose not to share infrastructure even when it is available, often because cost savings are not sufficient to justify the business risks.
The questions raised by this article are:
- What costs are being avoided by runtime sharing of common code?
- Are the cost benefits sufficient in the case of common internally-developed code to justify the runtime sharing?
The answers will depend on the business, but in general, it is hard to justify the increased risk. If common code is to be shared at run time, then it is imperative that each application team leader be made to understand, agree and strictly adhere to specific constraints on their ability to request change to the common code. They must accept that in a crisis situation, where two or more application teams have opposing interests, that a third party with business authority over both will be the one to decide what is best for the overall business, not the individual application. At a minimum, it is strongly recommended that the applications that share common runtime code all belong to the same business unit.
Finally, this article has assumed significant knowledge regarding J2EE and JavaTM classloaders. Readers needing to learn more are encouraged to check out the suggested reading in the References section.
For those rare occasions where there is code that is truly used only by a single WAR or EJB JAR it can be placed in those modules.
Of course, you will need to update the module dependencies to express that the EJB JARs and WARs depend on the library in the EAR. In WebSphere Studio, simply right click on the module and select update dependencies. See the first two articles in the References section for details on using WebSphere Studio.
Shared code could also theoretically be placed on the application server classpath, resulting in the code being shared by all applications on that application server. However, this is strongly discouraged, since code placed here is not dynamically reloadable and has no visibility into the WebSphere Application Server or generic J2EE APIs, making this classpath useless.
- Developing and Deploying Modular J2EE Applications with WebSphere Studio Application Developer and WebSphere Application Server by Rick Robinson
- Developing J2EE utility JARs in WebSphere Studio Application Developer by Tom Deboer
- J2EE Classloading Demystified by Tom Deboer
- J2EE Packaging and Deployment book excerpt