Java Servlets and JavaServer Pages (JSP) threaten to collapse out there in the mud houses of server land. Developers succumb to the temptation to muddle their pages with complex business logic, and to fill behemoth proprietary libraries with data and subsystem access routines. We find stronger solutions in EJB-based architectures than in fattened JSPs, but molding existing sites into EJB-centric designs strikes most of us as a daunting and expensive task.
However, there are clear guidelines for evolving the JSP and custom code approach into a more flexible EJB-based architecture. Once convinced that the endeavor is both worthwhile and feasible, developers can take steps to pare down servlets and JSPs to simple MVC-compliant presentation layer elements that elegantly access hard-core logic in the form of decoupled and refactored EJBs. This is the practice of growing a JSP site into a full-blown J2EE application.
In designing software, most of us will agree that mixing presentation logic and business logic is a naughty thing to do. In practice, however, JSPs do not naturally lend themselves to a decoupling of these layers, often leading us to compromise our design aesthetics in favor of implementation convenience. After all, we're not HTML designers. We know Java, and we don't mind plopping Java routines right in the middle of our pages. It's quick and dirty. Eventually, as the business logic in the pages grows more complex and touches more external subsystems (databases, mail servers, custom proprietary servers), our cool apps coalesce into great balls of mud. Figure 1 illustrates such a scenario, in which the client event handlers perform business logic that includes enterprise-level resource usage.
It's not that a pattern has become obscured in such cases; it's that an anti-pattern has emerged. Anti-patterns begin as compelling solutions that result in the eventual amplification of the problems they address. In particular, many JSP and servlet sites follow the Blob and Stovepipe anti-patterns, in which originally compelling code spirals out of control and sprouts numerous tightly coupled subsystems. Designers, developers, and engineers step on one another's toes to turn simple systems into sophisticated ones. The Web apps developed in this manner are akin to a house in which cleaning the windows inexplicably causes the roof to leak. The situation grows even uglier when new teams take charge of maintaining the code. Justifying the code Blob under the "Controller" MVC label doesn't resolve the practical problem, even if it sounds promising in high-level architectural discussions.
Lengthy procedural code is more difficult to test than object-oriented code, and dropping business logic into JSPs and servlets eventually results in just this sort of procedural code. Unit and regression testing is a snap when a specific function is isolated in layered component architectures, but QA balks at tightly coupled systems that do not readily expose functionality to clean test cases.
To resolve these problems, many developers take the pre-EJB route of moving complex business logic out of a few JSPs and into chained servlets and separately included JSPs, and eventually into proprietary libraries and utilities that their pages invoke. Proprietary libraries typically consist of scores of classes linked in complex inheritance schemes that grow increasingly convoluted over the library's lifetime. These libraries in turn may grow to call upon bandied-together O/R tools, or transaction engines. Figure 2 illustrates this scheme, in which proprietary libraries assume the burden of security, transactability, persistence, and resource connectivity in addition to the application logic. This approach not only exacerbates the issue by spreading the problems out among a larger code base; it also implies instant legacy code - costly and confusing to maintain, not portable, proprietary and interoperable with nothing beyond its own boundaries. In anti-pattern terms, the Stovepipe systems and Blobs are eventually covered by unmaintainable lava flow.
Tag libraries, or taglibs, are a terrific solution for many problems caused by typical JSP usage, but when taglibs contain hard-core business logic - requiring transactions and concurrent processing, for instance - they suffer the same problems associated with all proprietary code libraries. Taglibs are excellent presentation layer elements that cleanly broker communications to portable remote business logic components, but they should not contain business logic and resource connector code. Your tag-loving Web designers will be much happier with a handful of powerful tags that can access business components rather than hundreds of overly specialized tags that perform the business logic themselves.
When masterfully employed, servlets and JSPs conform to event-based rather than component-based patterns. Servlets are event processors; in comes a request, out goes a response. They thrive in pipe-and-filter designs, in which the request and response types and the sequence of pipes through which they flow define the system functionality. Therefore they thrive as the transformers of client/server communications.
Enterprise business logic, however, is well suited to component-based design and its implicit decoupling of system and application functionality. System functionality, including security, transaction and life-cycle issues, are managed independently of business logic invocations. EJB components don't rely on request and response types or stacks of user-crafted filters to define their environmental functionality; instead, they rely on their container for such things, and the business logic remains separate.
Either one of these models alone won't fit the bill. In a complex enterprise application, we require connected subsystems that follow both models. In most real-world cases this means pulling logic out of JSP/servlets and out of any dependent proprietary libraries they may rely upon.
EJB Design Best Practices
Once convinced your application would benefit from EJB usage, how do you go about making the change? Refactoring, or methodically improving the design of existing code, is the recommended approach, and EJB design principles provide the engineering ammunition.
There are several emerging strategies for EJB design, covering topics from the granularity of data access to inheritance to caching. At a high level, refer to the following design principles and EJB best practices:
- Session EJBs are extensions of the client into the server, and should be used for any logic that the JSP/servlet application previously executed.
- Entity EJBs are representations of data that exists in an underlying datastore, and should be used to encapsulate logical groups of data (such as a customer, a company, etc.) as well as any logic inherent in that data collection. For example, include logic that returns different values depending on whether a specific customer entity is a regular customer or special customer.
- Resource factories provide a portable means for accessing subsystems such as mail, messaging, and the like. Use resource factories through JNDI rather than directly accessing a resource through explicit delegation or hard-coded references.
- JavaBeans, whether exposed in JSP pages or encapsulated in taglibs or GUI elements, provide excellent places to store client-side references to remote EJBs.
- Client interaction with application logic and resources exists in a fluid, contextual data structure that may be transformed either explicitly or implicitly into EJB value objects, JavaBeans, files, or database tables. Even when not explicitly architected, this context exists in some form across all layers in an enterprise application.
- Entity EJBs should be coarse-grained, with one entity representing a large type of data grouping rather than a number of smaller types. For example, craft one customer entity rather than separate entities for each type of customer.
- Entity EJBs should contain aggregate get/set methods that return chunks of data rather than fine-grained get/set methods for individual attributes. For example, instead of creating getStreet()/setStreet() methods along with getCity()/setCity() methods, create a single getAddress() method. This eliminates frivolous database, transactional, and network communication overhead.
- Decouple EJB access from your pages and servlets. At a minimum, use a tag in a taglib to call an EJB and execute its methods; also consider relying on a JavaBean decoupled from your taglib, where that JavaBean contains all the EJB access code.
- Avoid stateful session beans in large public enterprise applications (as opposed to intranet or small-distribution applications). Stateful session beans are resource-heavy, since one instance is maintained for each client. In Web-based applications, JavaBeans scoped to the user session or taglibs often provide reasonable lightweight alternatives to stateful session EJBs.
- Under heavy loads, entity beans should do more than merely represent a table in a database. If you are merely retrieving and updating data values, consider using JDBC within session beans instead.
- Consider the network and your hosting facilities while architecting your particular software. If you have one large database host but only a small Web and middleware host, consider moving much of your logic into stored procedures and calling them via JDBC in session beans. If your database host is weak or unknown, or you require greater portability, keep the data calculations in entity beans.
- Most J2EE server vendors provide optimization parameters for improving the overhead of EJB loading and storing. Use them. At minimum, these optimizations should follow the three commit options described in the EJB specification.
- Consider using a single stateless session bean to provide access to other EJBs (this is a fašade pattern). All of your EJB clients request operations through this session bean rather than making calls and obtaining references to multiple EJBs. This not only satisfies aesthetics, it also optimizes multiple EJB references and calls by keeping them in process, useful for local beans in particular.
- Merely porting a large body of code into a few EJBs won't resolve anti-pattern problems (EJB is not a silver bullet). You should still segment business logic into sublayers, where each sublayer is composed of EJBs with different functions; for example, consider an access layer to present a public API, an invocation layer to handle references and lookups, a logic layer to handle application algorithms and an entity layer for data representation.
- Monitor pattern sources, such as the J2EE Blueprints and the Server Side, for specific best practices, designs, and case studies applicable to your projects.
There are two general approaches to the actual JSP-to-EJB refactoring, and each is equally viable. The top-down approach involves starting with the presentation layer, while the bottom-up approach involves first sorting out the data access layer and its collections. The choice of strategy is largely a function of your personal engineering approach and the nature of the project at hand.
The top-down approach is common to Web-heavy applications and to developers employing an Extreme Programming methodology. This strategy asks you to start with your users and the presentation elements they see. Take a look at all the Java code in your JSPs. Pull that code out and group related functions into methods. Stash these methods in tag libraries. If you are using a proprietary code library, identify the presentation-related methods in your library's classes and refactor them into a taglib. Replace the Java code in your JSP pages with these tags.
Next, identify the heavier business logic that is in your taglib and/or proprietary library, and move it into session EJBs. The easiest way to do this is to find the relevant methods and simply cut-and-paste them into a session bean implementation. Don't worry about writing the bean's remote interface first. Since you have existing legacy code, you are refactoring rather than writing an EJB from scratch. After you've moved the business logic into session bean methods, write the bean's remote and home interfaces to abstract those methods. Finally, identify the core-data components and pull them out of the session EJBs and into entity EJBs. More on those in a moment.
The bottom-up approach sets the Web site aside while first delving into the core-data requirements. The strategy for top-down refactoring applies in reverse. Pull the data access and collection code out of JSPs and taglibs and move them into entity EJBs. Got a Hashtable called customer? Sounds like an entity EJB to me. Then move the entity EJB accessor code (that is, the client code) out of the JSPs and their taglibs and into session beans. Finally, build any new tag libraries that you require for presentation layer transformations. The idea is that JSPs use taglibs or servlets to call session beans (or MessageDriven beans for asynchronous functionality) and session beans may call other session beans or entity beans in order to execute logic for the tags.
Figure 3 illustrates this scenario, in which the components contain only segmented application logic. The components delegate system logic to their containers.
Regardless of whether you pursue a top-down or bottom-up strategy, the most time-consuming element of this process will likely be refactoring your data access and data manipulation code into entity EJBs. In general, it's advisable to use Container Managed Persistence (CMP) entities rather than the Bean Managed Persistence (BMP) alternative. But again, you have legacy code; don't be afraid to work against the grain of standard practices.
If you already have JDBC code in your servlets, taglibs or session beans, first create a BMP entity. Work first with the bean implementation, skipping the remote and home interface creation for now. This approach means essentially cutting-and-pasting the JDBC code into the appropriate entity life-cycle methods. After you have the implementation, then write the interfaces (the reverse of the normal procedure). Creating a BMP entity EJB from existing JDBC code in this manner is extremely quick, and after you've fashioned such a BMP entity, you can further refine it to make use of CMP, which typically provides better performance (due to data caching) in addition to its obvious portability.
Another advantage CMP offers is that the EJB is independent of any particular database and datastore type. It may rely on a relational database, but it may just as easily rely on SAP or some other ERP system with no changes to the bean code. The EJB need not specifically worry about such things, and is therefore portable (a very attractive feature of EJBs). EJB QL, the datastore-agnostic query language for CMP entities, standardizes this abstraction syntactically under the EJB 2.0 specification. In most vendor implementations today, regardless of whether they support EJB QL, transforming a BMP entity into a CMP entity means moving the SQL code out of the JDBC routines and into some object-relational mapping facility, such as a vendor-specific XML file.
Refactoring isn't complete without a repackaging of your application according to its functional units. Enterprise applications consist of JSP, servlet, and taglib files collected into Web archives (WARs) and EJBs collected in Java archives (JARs). The format for this collected application is the Enterprise Archive (EAR). Think of an EAR as a molecule, where the JARs and the WARs are the molecule's atoms. The metaphor extends further to compare the EJB's internal pieces to subatomic particles. This is no mistake - J2EE apps follow a deliberate component-centric design that mirrors beauty in nature. Components consist of components.
You can mirror this in your packaging. Instead of merely deploying the layers of your application individually (a Stovepipe process), package them as a logical organic service (as shown in Figure 4), which can be deployed on any certifiably compliant J2EE server.
Applying MVC to Enterprise Services
Three basic categories exist in the software patterns community: architectural patterns that define high-level frameworks (such as the layer pattern used throughout this article's illustrations), design patterns that outline system and subsystem strategies, and idioms that describe language-specific tactics. Among architectural patterns, few have garnered as much attention as the MVC pattern, which originated in Smalltalk user-interface design. The idea is that the presentation View is cleanly separated from the data Model that it presents, and that both data and presentation are refereed by a Controller containing business logic. Figure 5 presents some examples of MVC implementations, in which a Controller EJB sends requests for logic to other EJBs in order to create or transform the Model, which represents the data. The Controller returns relevant pieces of that data to the View, which prepares it for client-specific display.
The Controller fits snugly into the application logic layer of our enterprise service. The central Controller object does not necessarily execute the business logic itself, but it's at least the entry point into the business logic. A stateless session bean is a good fit, as it scales well and can efficiently pass off requests for heavy logic to other EJBs. This Controller session bean could be accessed directly by tags in a tag library, or by a servlet.
The Controller is the common Blob culprit in failed MVC implementations. Often when logic is removed from the presentation tier, the Controller grows fat and convoluted as a result. To avoid this, follow the aforementioned best practices, and segment the Controller into sublayers. Then apply proven design patterns to each of these segments.
The WAR component of an application, which includes the JSPs and taglibs, is the View of a Web-based service. If you find business logic winding its way into these elements, then you're flirting with violating strict MVC.
The Controller updates the Model in accordance with messages received from the JSP-based View. The Model may be a heavyweight entity EJB, or value object, or a lightweight data collection that the Controller sends as a parameter to EJB method invocations. Often the Model is all of these things at different periods of its life cycle; as the Model passes through layers it may be transformed into any number of implementation constructs. Through method invocations, the Controller transforms the Model and returns the Model to the Controller. Whether the Model is best represented as an entity bean, collection, or value object is a complicated design decision that's dependent upon the size of the data structure, the transactional attributes associated with it, and the number of method calls necessary to manipulate it. Multiple invocations on remote objects such as entity beans result in high network usage, but large data objects are much bulkier to send across the wire than entity EJB stubs. Let your project analysis determine the correct implementation.
In the end, users view pages, which are created by designer-crafted JSPs and their developer-crafted custom tag libraries. As users navigate through content, the taglibs send messages to the developer-crafted Controller code. This Controller translates the user's actions into requests for application logic; the Controller executes the logic by calling various developer-crafted EJBs. In this scenario, all of the EJBs are part of the Controller. In a long-lived session, the Model isn't re-created but continuously transformed by the Controller EJBs and remembered between calls by the View. The Controller sends pieces of this Model (or even the entire Model) back to the taglibs for translation into presentable elements on a JSP page.
The Future of EJB-Based Services
Once JSP-based applications are refactored into independent but connected services, the app is prepared for swift evolution. As vendors create new resource adapters, developers add entirely new subsystems to apps without interfering with any existing functionality; obsolete logic is removed without interfering with the Web presentation; new EJB components are purchased, removed, and added to existing services; entire applications are tied together and ported across unrelated enterprises; legacy data in mainframe systems filter effortlessly into existing services. The possibilities are powerful when the design and implementation are atomically elegant.
The definitive guide to refactoring strategies, complete with a catalog of refactoring patterns and idioms, is Martin Fowler's Refactoring: Improving the Design of Existing Code (Addison-Wesley, 1999). Strong EJB books are difficult to come by, but Ed Roman's Mastering Enterprise JavaBeans is a good resource, and its latest incarnation is available for public review at www.theserverside.com. That site and www.javasuccess.com are both good online sources for advanced EJB-specific design and patterns.
Patrick Sean Neville is principal engineer at Macromedia, where he is the architect of JRun's EJB server, core-service infrastructure, and other enterprise components. Prior to joining Allaire/Macromedia, he designed and implemented distributed applications for
companies ranging from media agencies and Web start-ups to banks and
financial institutions across the country. He is the creator of the Code Studio (www.codestudio.com), a supporter of open-source endeavors, and a frequent author and speaker on J2EE topics.