|IBM home | Products & services | Support & downloads | My account|
|Deploying multiple applications in J2EE 1.2|
There is a serious deployment problem surrounding the building of J2EE applications, which has unwittingly been propagated by many of the books and articles written on the subject. The problem is caused by the difference between the requirements of a project that will fit within the context of an introductory book and the requirements that projects have in the real world. In short, the issue is this: You never have just one project, and projects are never completely isolated from each other.
In the real world, you don't deploy a single project and rest on your laurels. Reality is far more complicated. You often deploy one project and then two months later deploy another project that relies on the services provided by the first project. Sometimes you deploy a project and then six months later deploy version 2 of that project while keeping version 1 running concurrently for a period of time.
If you are an Application Service Provider (ASP) it's even more complicated. In this case, you may actually deploy the same application several times for different customers, with only the entry point URLs and the details of the static bitmaps and Web pages differing between customers.
Once upon a time . . . (an
application development fairy tale)
But then along comes a new requirement. We're asked to build a new application that allows an employee to select various parts of his benefits package, allowing him to sign up for medical insurance, a savings plan, and so on. For this application, we need exactly the kind of business logic that's already in the previous application. For instance, to allow the employee to buy and sell vacation days, we need to know his years of service -- something the EmployeeManagement session bean can already retrieve for us.
So what do we do? Do we rewrite the logic a second time, or do we find a way to reuse the existing logic? Rewriting certainly doesn't seem like the right thing to do, so reuse seems to be the most efficient option. But how do we reuse the session bean and its associated classes? Let's examine a few options.
Option 1: Merged-component
What we've done here is to place the two applications inside the same EAR so that the Benefits application can access the classes and EJB components in the Timesheet application. This approach is very easy to accomplish, and it solves our immediate problem. However, it has some drawbacks that make it unattractive as a long-term solution.
Let's consider the way the two applications are used. The Timesheet application is used regularly, every day, by every hourly employee in the company. That means that we can easily predict the peak load and determine the appropriate hardware capacity that the application should be deployed on. But the Benefits application is different. Every employee in the company (hourly or not) will use this application, but only once for each employee, and the use could occur at any time within the two-week benefits signup period. So the hardware capacity needed to run this application could be very different from the Timesheet application -- it might be significantly higher if everyone waits until the last moment and signs up on the last day of the enrollment period (although we're sure that would never happen).
What's more, the use patterns of the applications differ -- the Timesheet application is in use during regular business hours every day, leaving evenings free for server and application maintenance. However, the Benefits application should be available at night for employees to use over the company's Virtual Private Network (VPN) so that they can involve their families when making their benefits choices. So how do we reconcile the different requirements?
Also, what about availability? In our example, it may be acceptable if the Benefits application goes down for an hour or so, but not the Timesheet application -- that's required to be up 100 percent of the time during business hours.
Drawing from these requirements, we can conclude that the applications cannot be deployed within the same EAR file; they are related, but not closely enough to warrant co-deployment. In fact, evaluation of the previous considerations will lead you to reject merged-component deployment as a deployment solution in most cases, which brings us to shared-service deployment.
Option 2: Shared-service
This approach treats the EmployeeManagement bean as a shared service. Deploying the applications into separate EAR files lets us deploy them into different application server JVM instances, which in turn allows us to make different decisions as to how much hardware to allocate to each application. What's more, this approach seems to help the maintenance problem -- we can now bring down the Benefits application without affecting the Timesheet application at all.
However, we haven't fully solved the problem. We now have a dependency between the two applications. While we can bring down the Benefits application, the Timesheet application must now have the same availability requirements as the Benefits application, creating a potential administration hassle. Furthermore, we now have to deal with a problem that's not addressed by the J2EE 1.2 specification: how to include the EJB stub code in the Benefits WAR file. Theoretically, we could separate out the client code from the Timesheet EJB-JAR file, but that seems error prone and likely to create problems. We'll come back to this possibility later.
Another possibility is to locate the client code outside of the EAR file on a shared classpath, but this approach not only violates the spirit of the J2EE specification, it can wreak havoc in our applications and the application server itself if we choose the wrong classpath. In short, this solution creates many more problems than it solves.
Shared-service deployment can degrade performance when compared to the merged-component solution. If we choose to separate the two applications by placing them into different JVMs, we require a cross-process call that could have been optimized out by the container in the solution where both applications were located in the same JVM. In short, calling an EJB component within the JVM it is deployed in is several times faster than calling it from outside that JVM.
However, there is a more insidious problem that this solution creates, which was also present in the previous solution: version drift. Imagine that in the initial version of the EmployeeManagement session bean we had a method with the following signature:
This method signature assumes that
Because the Timesheet team is the one developing this EJB component, they plan to update their application to use the new employee ID signature. But what about the Benefits application? Suppose the Benefits application only rolls out a new version once a year right before the benefits signup period begins (because we can only change benefits during the signup period, but we can view our benefits at any time). If the benefits team isn't ready to come out with a new version of their application, then this change to the Timesheet application will break the Benefits application.
So how do we avoid the API version drift problem? To find the solution, we need to think "outside the box" a bit. While it may appear that the J2EE specification has locked us into a series of bad solutions, it, in fact, contains some hidden wisdom that allows us to solve this problem quite elegantly. The key to solving our problem is to conclude that the pieces of logical projects should not cross EAR files if at all possible. Let's take a look.
Option 3: Independent
To understand how this deployment option works, let's take a look at the way it's implemented in IBM WebSphere Application Server, Version 4.0. Application Server has a single "global" JNDI namespace that is managed at the domain level. The JNDI naming service in Application Server, Version 4.0 runs on the Admin server, so while we have one JNDI service per node, they all share the same global namespace, which is held in the common administrative database. Although the implementation is different, Application Server 5.0 also has a global and a local namespace so the principles are the same in the new version. However, there are two parts to this: local references (for instance, java:comp/env entries) are unique to a specific application because they are specified in the web.xml and ejb-jar.xml files. That means our code can refer to these names and be assured that it will not have to change if the configuration changes underneath them. The second part, then, is that these local names are bound (at deployment time) to names in the shared, global JNDI namespace. So we could have the case shown in Table 1:
Table 1. Local names bound to names in the global JNDI namespace
This approach solves a number of problems that occur when trying to split applications across EARs -- most notably the "API version drift" problem that occurs when we have two applications that each depend on a slightly different version of the shared EJB component. The argument that we often hear against this solution is that it "results in too many EJBs" and that it's wasteful of memory. That's simply not a valid argument. We can tune the cache sizes of our EJB servers and containers to be even more effective at memory management in this scheme than we can in the shared scheme. Also, many people not familiar with EJB technology (entity beans especially) think that this solution won't work because there is something "magical" about the way entity beans operate -- that somehow there's only ever one entity bean object for any piece of data, and that deploying beans in multiple ways like this will wreck our data management. That is also false, because the same problems of row locking and isolation level apply when we have multiple clones of an application in a clustered environment like we have in this solution. Concurrency is managed in entity beans at the database level, unless we're using EJB commit option A, as defined in section 10.5.9 of the EJB 2.0 specification (see Resources), which generally does not work in a clustered environment. In any case, choosing this approach will not affect the consistency or the access speed of our data in the least.
However, there is another potential problem that can still create issues with this solution. In solving the "API version drift" problem, we've left ourselves open to a more subtle "data/external drift" problem. Some applications now use older versions of the EJB code than other applications. It is quite possible to make changes to a database schema as part of implementing the newer version of the code that might break the old code. Unless we want to keep two copies of our production data (a data management nightmare), we must be very careful about implementing database schema changes.
Also, there is an even more subtle issue regarding changes to the business logic. Often a change to an external interface represents a change in the semantics of the way an API works. However, sometimes semantic changes occur without a change to the external API -- for example, a critical bug might be fixed, or a complex piece of business logic might be refactored for better performance. The major issue in this case is that if we choose independent deployment, we can deal with changes of this sort only by updating all the other EAR files. Thus, shared-service deployment is actually preferable in some cases where this may happen.
In general, the more complex the application is the more difficult it is to implement independent deployment. Complicated applications often have complex management, installation, and initialization procedures (many configuration files, Application Server configuration constraints, and so on). In this case, combining them with other applications places a great burden on the client application. It's easier simply to provide the client application with the appropriate client EJB-JARs. Then the client only has to deal with the client interfaces and not the remainder of the client application. Of course, for simple applications it is far easier to combine them than to deal with the complexities of a distributed environment. Many organizations never even consider independent deployment, even when it could save time and effort.
If you must implement shared-service deployment, how do you make it work, considering that the existing J2EE tooling does not directly support that option? In fact, you have to work around the tooling. One option is to develop your own custom tooling capable of "stripping" a standard EJB-JAR file and packaging a new client JAR file. At a minimum, removing the ejb-jar.xml file from an EJB-JAR file will render it into a client JAR file, since the development tooling will not attempt to deploy the EJB components inside the JAR without the information held in the ejb-jar.xml file. However, in most cases you will want to strip out additional pieces of the JAR file to make it as light as possible (including, for instance, the EJB implementation classes and the generated skeleton and persistence classes).
Finally, another variation of shared-service deployment that we need to consider is the possibility of completely splitting out the shared components (in our case the EmployeeManagement bean) into its own EAR file, totally separate from any other application-specific code. This is probably the best long-term solution for applications where shared-service deployment is the best option. However, this presents its own set of concerns: Once the components are separated from the application code, who now owns them and is responsible for maintaining them? If a special reuse group has been set up to maintain the shared distributed components, then this can work well, but if not, then this option can result in a morass of finger-pointing and declarations of "not my job" when things go wrong with the shared component.
Where to go from
In fact, this last point deserves a little more clarification and some additional hints. One of the practices that we encourage is that J2EE components (EARs, WARs, and JARs) contain additional, human-readable metadata that indicates the versions of the various subcomponents that comprise it. So, for instance, an EJB-JAR file might contain a special XML file (not used by the application server, but meant to be read by humans and possibly generated by an SCM tool or script) that lists the version numbers of the different EJB components it contains. If an EJB-JAR or WAR file depends on other versions of other J2EE components, it should include that information in its XML metadata file as well. Likewise, an EAR file should contain the list of versions of the WAR and EJB-JAR files that it contains. That way, version mismatch problems can be caught automatically when the EAR is built. If a mismatch is detected, the build can abort before it is deployed.
While we would recommend that you adopt the simplest solution (independent deployment) where it applies, we have also given you the tools necessary to understand when it does not apply and instead requires a more involved approach. This should help you determine how to best configure your own application servers and how to avoid some of the more painful problems that improper deployment can cause.