Design

In this section, we go through the process of designing several EJBs. While the design process of an EJB application is 95% identical to the design process for a non-EJB application (maybe even 99% identical), there are some steps in this process that require special attention.

To discuss design, we need to change our thinking a bit. Throughout this book, we have focused the details of EJBs and how their individual components work. In this section, we consider the Titan EJB application as a system meeting a business need, and not simply as a collection of fine-grained components. We will look at the design of such a system from the ground up, taking the application—as a whole—rather than continuing to view only the EJB components themselves (though we’ll obviously pay special attention to those components, since this is a book on EJBs). Let’s start by looking at its requirements.

At a high level, the application will be used by:

  • Travel agents to sell reservations

  • The general public to view cruise details

  • Cruise administrators to manage the application’s ship and cruise data

The application will be accessed via three mechanisms. The first two mechanisms are for “person” users (as opposed to “system” users, described below):

  • Web interface (general public, travel agents, and cruise administrators)

  • Standalone Java application (travel agents)

The third access mechanism is for systems that need direct access to the business layer. For our application, this includes access by:

  • External travel agency systems (which includes both travel agents not working for Titan and reservation distribution services that act as clearing houses for cruise line availability)

  • Ship provisioning companies that need to know physical specifications for Titan’s ships in order to provide auto-ordering of provisions (ship capacity, fuel type, and so on)

All three communications mechanisms (web client, standalone application, and business-to-business) must allow only secure actions to be executed by the users. Connectivity to the external travel agencies and to the ship provisioning vendors is not guaranteed, so the communication mechanism will need to handle disconnects. Finally, we want to generate reservation confirmations and other forms in PDF format. Figure 19-1 is a system diagram for our requirements so far.

Application system diagram

Figure 19-1. Application system diagram

Business Entity Identification

Now that we know our application’s requirements, at least at a high level, we can identify the key business entities the application needs to represent. This is generally a lengthy process, and we will only go over some of the results here. While in our example this is presented as a step-by-step, one-time process, it is really iterative. You will probably take a first stab at identifying business entities, and then go through the process again and again before having a final list of all your business entities.

Here are some of the business entities for the Titan application:

Reservation

Reservations are created by Travel Agents and belong to a Customer. They are associated with a Cruise and zero or more Cabins. A Reservation has a financial subtotal.

Travel Agent

Travel Agents create and update Reservations and view Cruise information. Travel Agents are a kind of Person.

Customer

A Customer is also a kind of Person. Customers have zero or more Reservations.

Ship

A Ship has zero or more Cabins and belongs to zero or more Cruises.

Cruise

A Cruise has a Ship and a date period and is associated with zero or more Reservations.

Cabin

A Cabin belongs to a Ship and is associated with zero or more Reservations. All associated Reservations must have a Cruise with a Ship that matches the Ship for the Cabin on the Reservation.

There’s more structure to this list than is immediately apparent. It follows a number of guidelines that help reveal the important aspects of each entity:

Capitalization

Business entities are capitalized, while simpler pieces of information (date period, subtotal) are not.

“Kind of,” “belongs,” “has,” “is associated with”

These phrases indicate fundamental connections between two entities. We have guessed at specific connection types for now, though the reality may change as we proceed. “Kind of” may indicate inheritance. “Has” and “have” may indicate that an entity is the parent in a parent/child relationship, while “belongs” may indicate that the entity is a child of another entity. “Is associated with” is a relationship too, but with a weaker sense of ownership (i.e., not parent-child).

Concrete verbs

There are three concrete verbs (create, update, and view), all in the description of the Travel Agent business entity. These verbs indicate processes or significant responsibilities handled by the entity.

Since we focus on the components that will end up being EJBs, our functional analysis is complete: selecting business entities is the most important part of the EJB design process.[58]

The next step is to look at the technical architecture and its implications for our entities. We’ll get to that in just a moment. First, let’s take a moment to diagram our business entities using UML so that we have a clear understanding of their relationships. While the textual descriptions help define the business relationships, a UML diagram depicts them more exactly. Figure 19-2 is a UML diagram of our business entities and their relationships.

UML diagram of the application’s business entities

Figure 19-2. UML diagram of the application’s business entities

The UML diagram introduces a Person entity from which we will derive both the TravelAgent and Customer entities. We’ve also introduced a mapping entity for mapping Reservation entities to Cabin entities. Otherwise, the UML diagram states exactly what we described earlier in the text.

The next step is to consider which entities to implement as EJBs, and what types of EJBs to use. But first, it will help to understand the architecture of the system as aspects of that technical architecture will have direct implications on our entity implementation choices.

Technical Architecture

Earlier, we depicted our system in a high-level diagram (Figure 19-1). This diagram depicts relationships among various entities and our system, but not much more. What else do we know about the various interactions of these entities and our Titan application?

  1. We know that connectivity between the external travel agencies and our system is not guaranteed. Since we are working with a Java implementation of this system, we may want to consider JMS as the communication mechanism between our system and theirs.

  2. Furthermore, we know that communication between our application and external travel agencies will be two-way (our application must be able to accept reservation requests), but communication between our application and the shop provision entities need only be one way (we will tell them how many people are attending a cruise, for example).

  3. From our initial description we can infer that making reservations is transactional and involves the following steps:

    • Reserve a Cabin for use by a Customer.

    • Reduce the total number of Cabins by one.

    • Increase the total number of Customers for whom the provisioning vendor must provide food.

  4. These steps could involve up to three different database tables. At a minimum, this might involve the following database objects and systems:

    • One to store Reservations.

    • One to store Cabin availability.

    • One to store Customer information for provisioning.

    While the complexity of these operations is not clearly defined, we can assume that reservations systems and the management of cruise and ship data are probably of moderate to high complexity. When combined with the need for transactional enforcement and the fact that only certain users will be able to execute certain actions (implied), using EJBs to represent the entities is appropriate.

  5. We know that customers and travel agents will be able to access the Titan application over the web. This indicates that part of our system will involve controlling a user interface. EJBs are not well suited to user-interface work, so we’ll include the use of servlets and JSPs in our system view.

  6. We also know that travel agents will be able to further access the system via a standalone Java application, indicating that some of the communications with the business tier of our application might not come via the Web.

Using this information, our technical system diagram can be amended as shown in Figure 19-3.

Amended system diagram

Figure 19-3. Amended system diagram

While it may not look like it at first, we’ve gotten much closer to identifying our EJBs. Between the new architecture diagram and our business entity UML diagram, we have all we need to move forward.

EJB Identification

Not all of our business entities will turn out to be EJBs, so the next step in our design process is to identify which of them should. Our understanding of the application’s technical architecture helps. This is not a simple or well-defined process, like completing a jigsaw puzzle or building a bridge. For all but the simplest of applications, the process of identifying EJBs in the application’s technical architecture presents ambiguity and conflicting requirements. It’s not easy to make the right choices. Fortunately, there are several rules of thumb that will help guide the process.

Let’s quickly review the EJB types:

Entity beans

Represent records persisted in a database. Entity beans can often be used to represent the nouns or things from our functional description. If a business entity has a real-world counterpart, it is probably an entity bean.

Session beans

Manage processes or tasks, often calling other EJBs and non-EJB business objects. Represent taskflows. They are invoked locally or via RMI, both synchronous mechanisms.

Message-driven beans

Manage processes or tasks, like session beans, but are invoked asynchronously, via JMS or possibly another messaging system. A message is received by the system and some function of the MDB is executed.

Identifying entity beans

With these characteristics in mind, let’s start out by identifying entity beans in our application.

Guideline #1

The description of entity beans gives us our first guideline: entity beans represent the entities (significant nouns) from the functional requirements. They are rows in a database table.

Our class diagram was created from the list of business entities in our functional analysis, which are essentially the things in our functional requirements. We know right away that the components in the diagram are all candidates for implementation as entity beans. However, not all of them should be implemented as entity beans. Entities that are read-only may be best implemented using one of the EJB alternatives, such as JDBC or JDO. Read-only entity beans can take advantage of caching and other vendor-specific optimizations that your container may offer, but they really don’t need the transaction enforcement that EJB provides.

Other factors to bear in mind when making this decision are your team’s skill set, the performance ramifications of the options, and the relative amount of functionality implemented in the options.

You should also avoid logical inheritance with entity beans, in which one entity bean, Customer, is a subclass of another entity bean, Person, and could be cast to the parent’s type. While inheritance works all right for sharing common EJB implementation code between different EJBs (see the “Base and Utility Classes” section below), never try to implement logical inheritance with entity beans. The most important reason to avoid logical inheritance derives from the fact that entity beans correspond to rows in a database table, and inheritance, as an object-oriented concept, is foreign to the database world. You can’t define tables CUSTOMER and TRAVEL_AGENT to inherit the attributes of a third PERSON table. Moving back to our entity beans, our best option is to remove the inheritance relationship and replace it with a composition relationship, which is functionally equivalent. Which brings us to the next guideline.

Guideline #2

Guideline #2 involves using composition between entity beans instead of logical inheritance. This means the Customer and TravelAgent entity beans will have a corresponding Person entity bean. Figure 19-4 shows the updated class diagram.

Updated class diagram

Figure 19-4. Updated class diagram

Identifying session beans

While entity beans are the things in our application, session beans implement taskflow. They are the processors and workhorses; they do stuff. We will identify them by considering the work that our application must do, and a good starting place is the responsibilities depicted in the class diagram.

Looking over the class diagram, we see TravelAgent has the following responsibilities:

  • Views Cruises

  • Creates and updates Reservations

  • Creates and updates Customers

In any application, functionality seems to clump around one or more entities. Such a grouping of responsibilities often indicates that a session bean is needed. And this gives us the next guideline.

Guideline #3

Each session bean encapsulates access to data that spans concepts as identified in the functional requirements analysis and initial technical architecture. So, when we see the clumping, as we do with TravelAgent in our class diagram, we know that a new session bean needs to be added to the design. However, the business entity (or actor)—TravelAgent, in this case—will not become the session bean. It indicates where a session bean is needed. The session bean represents the action the entity takes, not the entity itself. Think of the entity—implemented as an entity bean—as the subject of a sentence and the session bean as the sentence’s verb.[59]

As for the name for the session bean, a good way to think of it is to create a name that reflects a combination of the target of the action and the action itself. For example, the TravelAgent creates and updates or “manages” Reservations, so a good name for our session bean might be ReservationManager. The primary objective of the name is to communicate what the session bean does. As the session bean encapsulates the responsibilities, each responsibility corresponds to a method in the EJB. So, our ReservationManager session bean will initially have three methods: bookReservation, updateReservation, and cancelReservation. These names are also named intuitively, to suggest what they do.

If we follow this line of reasoning, we may think we need to have a separate session bean called a CruiseManager. However, the only interaction the TravelAgent has with a Cruise is to list it. Furthermore, it could be argued that in the overwhelming majority of the cases, the TravelAgent will only list Cruises when making a Reservation. For these reasons, it might make more sense to combine the Cruise functionality and simply add a new listCruises method to the ReservationManager.

The listCruises method stands apart from the other methods a bit, both in effect (it reads data while the other methods write data) and in direct object (it returns a collection of cruises while the other methods manipulate a single reservation).This suggests Guideline #4.

Guideline #4

If a given session bean has a method that’s almost always called in the context of another session bean’s function(s), combine the session beans or move the method.

We have now accounted for all the responsibilities depicted in the class diagram, but we haven’t accounted for all the functionality specified in the functional requirements. Creating the initial class diagram from the business entities initially misses functionality that has no “source” entity. For example, we’ve focused only on the reservations and the actions and entities around them. However, a reservation involves a Cruise that has certain characteristics. Some part of our application must be available to administer these Cruises. Cruises are made up of Cabins and Ships. Our administration functionality should focus on the management of all three entities: Cruise, Ship, and Cabin.

Our application revolves around travel agency functionality, but without the configuration of the cruises themselves, the travel agency functionality (creation of reservations, and so on) would be meaningless. Let’s add a general session bean around this and other (to be determined) configuration chores. While we may need to break this into multiple session beans later, we can start with one called ConfigurationManager.

Here too, we want to give it methods based on the functionality it encapsulates. Since no taskflows are detailed above, we will assume that all three items need to be created, updated, and deactivated. Thus, these actions (for the three entities) become nine initial methods:

  • addCruise

  • updateCruise

  • cancelCruise

  • addShip

  • updateShip

  • inactivateShip

  • addCabin

  • updateCabin

  • inactivateCabin [60]

We can now expand our entity diagram into the class diagram of Figure 19-5.

Entity diagram expanded into a class diagram

Figure 19-5. Entity diagram expanded into a class diagram

Identifying message-driven beans

Now we need to look for the message-driven beans in the application. As our review of the EJB types reminds us, message-driven beans (MDBs) implement taskflows like session beans but can be invoked asynchronously. Roughly put, they are transactional message handlers.[61]

Guideline #5

Each message-driven bean encapsulates related functionality that must be invoked in a transactional manner when an asynchronous message is received. So, in order to tell where we might want to use message-driven beans—the same as with session beans—we look for groups of functionality. However, for MDBs the functionality is usually initiated with the reception of an asynchronous message.

Here’s where our system architecture diagram helps us. There are two places where messaging takes place between our system and another (ostensibly external) system:

  • Between external travel agencies and the Titan application

  • Between ship provisioning vendors and the Titan application

As you can see from our functional requirements and the technical architecture diagram, our system receives messages only from external travel agencies, so we’ll focus on the travel agent functionality.

Since we’ve not been told anything to the contrary, we assume that external travel agent systems function like ours. Thus, ours should include all the functionality incorporated into ReservationManager. Additionally, the external travel agencies need some way to retrieve a list of ships and their cabins. This listing ability is included because the external travel agency systems can only communicate via messaging. This suggests that one or more MDBs could be used to implement this functionality.

For the Titan application, we will have two MDBs:

ReservationListener

The ReservationListener creates, updates, or cancels one or more reservation in response to a reservation function message.

QueryListener

The QueryListener retrieves cruise and ship data in response to a query message.

Compare the responsibilities of ReservationListener with those of ReservationManager. The cruise-listing behavior and the reservation-specific behavior are implemented in separate MDBs. Why? Guideline #4 tells us that if we are only going to execute a given piece of functionality in the context of a given process, we should combine that function with the others. This guideline is appropriate for session beans. To add another method to a session bean does not introduce any complexity to the bean. It’s just another method. However, in JMS-based MDB, you have only one onMessage function. While you can certainly have many different types of messages coming into the queue on which the MDB is listening, each message type must be processed separately. Each message type adds another significant condition to the MDB’s processing logic. Furthermore, the functionality represented by the various messages for the ReservationListener will be largely the same, but messages representing queries for cruise information might be different.

While we’re talking about JMS-based MDB, it makes sense to discuss the importance of message design. When a message listener is invoked, the only information it has is the message that it has been passed. In many cases, the message listener needs specific business information to do its work, and that information is packaged in the message.

Exactly how it is packaged depends on which message type you choose: javax.jms.Message or its subinterfaces (BytesMessage , MapMessage , ObjectMessage , StreamMessage , and TextMessage ). A general rule of thumb is to use ObjectMessage for messaging between systems that are guaranteed to be Java-based and to use TextMessage for messaging between potentially non-Java systems. Because ObjectMessage carries a full Java object, the data inside it is already structured for easy access by the MDB, whereas all but the simplest data embedded in a TextMessage (and the other types to varying extents) will generally have to be processed before it can be used (by a StringTokenizer, an XML parser, Integer.parseInt, or something similar).

On the upside, TextMessage (and maybe BytesMessage) is the most universal message type—every messaging system knows how to send and receive simple text (and also binary data). That said, you should investigate message types and their trade-offs before making final decisions.

Because we need to accept messages from the greatest variety of external travel agency systems, we will use TextMessage messages carrying XML payloads. While it requires a heavy XML parser when processing messages, it provides interoperability benefits that fit our needs.

We’ve now identified all of the EJBs in our sample application. Figure 19-6 is an updated class diagram.

Updated class diagram

Figure 19-6. Updated class diagram

EJB Details

Now that we have identified the EJBs in our application along with some of their methods, we are about two-thirds done with our design. So far, much of the design has flowed almost naturally from our business and technical requirements. The remaining third of the design is more difficult and requires some hard decisions.

Much of the remaining design work centers on determining each bean’s sub-type and interface type (remote or local). Our application has the following EJBs:

Entity beans

Cabin, Cruise, Customer, Person, Reservation, Ship, TravelAgent

Session beans

ConfigurationManager, ReservationManager

Message-driven beans

QueryListener, ReservationListener

We can ignore the MDBs, because they do not have sub-types or interface types—other than javax.jms.MessageListener. However, we must determine the sub-type and interface type for the remaining EJBs. The attributes are critical to your application’s design, as they dictate the overall usage and implementation of your core business components. For example, implementing an EJB with a remote interface requires that all invocations of that EJB must catch a RemoteException. It is not impossible to change these attributes later in your application’s lifetime, but it can be difficult. For example, if we change an EJB from a remote interface to a local interface, we need to review and possibly remove all the code that was catching RemoteExceptions.

With this in mind, let’s determine the sub-type and interface type of our session and entity beans. We start by listing the decisions, and then discuss the reasoning in the following sections. After reviewing the business and technical requirements, we implement the EJBs as indicated in Table 19-1.

Table 19-1. Types of session and entity beans

EJB name

EJB type

EJB sub-type

Interface type

Cabin

Entity

CMP

Local

ConfigurationManager

Session

Stateless

Remote and local

Cruise

Entity

CMP

Local

Customer

Entity

CMP

Local

Person

Entity

CMP

Local

Reservation

Entity

CMP

Local

ReservationManager

Session

Stateless

Remote and local

Ship

Entity

CMP

Local

TravelAgent

Entity

CMP

Local

You may have noticed that the two session beans are stateless with remote interfaces, and the entity beans are CMP with local interfaces. Let’s explore how this came about. First, we’ll discuss the reasons a particular session bean might be stateless or stateful.

Stateless versus stateful session beans

As their names indicate, the difference between the two sub-types of session beans is the maintenance of state. A common source of confusion is that we use similar words when we talk about web session state, as with servlets and other aspects of web-based applications. Session bean state is taskflow-related and should have little or no relation to the web or presentation tiers of your application. Session bean state is a way of sharing information between multiple methods of the same session bean. For example, the stateful version of ReservationManager contains the current Customer, so that it is not passed into the bookReservation, updateReservation, and cancelReservation methods (Figure 19-7).

Stateful version of ReservationManager

Figure 19-7. Stateful version of ReservationManager

Contrast that with the stateless version of ReservationManager, in which the current Customer is a parameter for those methods (Figure 19-8).

Stateless version of ReservationManager

Figure 19-8. Stateless version of ReservationManager

The stateful session bean appears more elegant when we need to call bookReservation, updateReservation, or cancelReservation multiple times. However, that elegance has a cost. Stateful session beans are slower and more resource intensive than stateless session beans. This makes the choice of stateful session beans a trade-off rather than a pure benefit.

Perhaps you’re thinking, “That’s a pretty balanced trade-off.” Unfortunately, stateful session beans are not as useful as they first appear. Remember that stateful session beans share information between multiple methods of the same session bean. But the methods in the session bean’s interface are coarse-grained enough that the application should only be calling one at any given time. Why would your code call bookReservation and then cancelReservation?

In our example, we wouldn’t. However, you will encounter situations where you will need to execute multiple methods on the same EJB. In that case, you should apply the Session Façade design pattern.[62] In essence, the Session Façade pattern manages a taskflow, and it can manage information just as a stateful session bean does. Even better, it offers the same transaction and security management between multiple EJBs. Thus, stateless session beans with the Session Façade pattern are preferable to stateful session beans.

Container-managed versus bean-managed persistence

The decision to use containter-managed or bean-managed persistence for an entity bean determines the bean’s persistence mechanism, affecting the bean’s implementation. BMP beans must implement their data access, while CMP beans are implemented by the container. BMP offers greater flexibility in the datastores your application can use and how your application integrates with them. CMP beans can only use datastores that the container knows about, usually those with JDBC drivers. The flexibility of BMP can be essential if you need to integrate with external systems, making a good match with the Java Connector Architecture. BMP is also an option with entity beans that require complex data operations, such as those spanning multiple datastores or multiple tables in one datastore.

However, this heightened flexibility has a cost: you must develop data access functionality yourself, rather than depending on the container. This means that BMP beans require more effort to develop and maintain. CMP beans are virtually guaranteed to integrate flawlessly with the container, while BMP beans may contain code that is not EJB safe. BMP beans that integrate with external systems should hide the operational and semantic differences between EJB and the external system’s technology. Otherwise, the external system may cause all kinds of potentially serious side effects, some subtle and unpredictable. Also, BMP code may not be portable—which makes sense for code that is specifically written for complex data operations or integration with external systems.

CMP beans don’t have these issues. Here are some considerations about CMP:

  • CMP is easy to build and maintain. It requires only the creation and maintenance of deployment descriptors and the rest of the abstract persistence mechanism.

  • CMP can persist to any JDBC-capable datastore, which is sufficient for most applications.

  • CMP and CMR are fully capable of implementing simple to moderately complex data operations, which will generally cover most of your needs.

  • CMP is fully integrated into the container, so there is less concern about dangerous code or unpredictable external systems. This also allows you to take advantage of vendor-specific features more easily.

Don’t use BMP beans unless the requirements supporting them are strong and clear-cut. If you are using BMP beans to integrate with external datastores, locate or create as much documentation as possible.

Local versus remote interfaces

Don’t use remote interfaces unless you really have to: it can’t be emphasized enough. Distributing your EJBs adds a whole layer of complication that is often unnecessary. There are the basic, only somewhat irritating issues, such as handling RemoteExceptions in your client code, and there are the complex, intractable issues, such as loss of performance and reliability when your components must operate across a network. One big complication is that remote interfaces (and the implementation they present) are often difficult to change, because the remote interface will be used by other systems or applications that may be resistant to change.

Our application clearly needs to be distributed: it must support the standalone Java client that our internal travel agents will use. In your application, take a long, hard look at any requirements that push you in the direction of distributed components. Approach such requirements as with BMP:

  • Understand the requirements in detail and validate them.

  • Determine whether the requirements truly merit being implemented as distributed EJBs.

  • Document the detailed requirements before initiating development in order to ensure agreement and to prevent scope creep.

If, after this process, you determine that you need distributed functionality, your next task is to identify which EJBs should be implemented with remote interfaces and which should stay as local interfaces. In our application, travel agents will use the standalone client to access the full range of application functionality. We already know that session beans are the workhorses of our application—which is why we have exposed the session beans via remote interfaces.

However, none of our entity beans use remote interfaces. Why not? Remember that session beans encapsulate taskflows that manage entity beans, especially when we make good use of the Session Façade pattern. If our session beans are well designed, there should be no need to access entity beans remotely. Also, recall that CMR requires the dependent entities have local interfaces. So we avoid remote interfaces for entity beans.

In that case, how do we pass the entity data, such as cruise information, across the remote interface? Good answers to this question are provided in Returning Entity Data from EJBs. The Transfer Object pattern is preferable to the other approaches when working with remote interfaces. Transfer Objects complement the strict interface of EJB components, and most EJB applications will have Java clients.

In the EJB list above, we chose to implement both remote and local interfaces for our session beans. While this does result in slightly more code to build and maintain, it is a good idea to use the local interfaces in the code that runs inside the application server, such as servlets or JSPs. The small duplication is worth avoiding the remote interface.

Thus, our interface recommendations are:

  • Use remote interfaces only if you must, and only for session beans.

  • Insist that entity beans have only local interfaces.

  • Implement local interfaces for session beans if there will be code calling them from inside your application server.

Fleshing Out the Design

Now that you’ve determined the major aspects of your EJBs, all that remains is to complete the design down to the class and method level. This is the same task you would do for any application, so we will not cover it here. However, this stage can undo or compromise a good EJB design if it is executed poorly. This section discusses the two most critical lessons we have learned to keep an EJB design in good shape.

Minimize transaction scope

As you flesh out your EJBs, especially session beans, make sure that your transactions have the smallest scope possible. By scope, we mean the number of operations executed and the number of components used. Operations executed inside a transactional context require more container management than nontransactional operations, and this management generally results in limitations and performance costs. The limitations depend on the container, database, and other transactional components of your application. Exceeding these limitations can create problems that depend greatly on the execution environment and the exact processing being done.

This variability often makes diagnosis and troubleshooting of transactional problems difficult, so the best approach is to minimize transactional scope during design or early in coding. Here is how to identify possible transaction resource problems:

  1. Understand the transactional capabilities and constraints of your EJB container, your database, and other subsystems. You should be concerned with what resources are limited during a transaction. Remember to check both the vendor documentation and any specification documentation.

  2. Identify the complex taskflows in your application. Focus on functionality that iterates through EJBs, aggregates through data, or chains EJBs (where one EJB calls another, which calls another, and so on) inside a single transaction.[63]

  3. Estimate the amount of processing that the taskflows will perform. Consider the data entities used in the taskflows, and determine the maximum number of each entity that your application will support.[64] This knowledge can help you determine how many EJBs will be used. Also, consider non-EJB resources, such as database cursors. Combine this data with the steps and dependencies of each taskflow to produce a list of resources used.

  4. Compare the list of resources used by each taskflow to the relevant setting or constraint. For example, the total number of EJB instances is limited by the max-beans-in-pool deployment descriptor setting. Where the resources used could exceed the available resources, you will need to minimize the transactional scope.

Repeat this evaluation if you make significant changes to your EJBs, especially after revisions that affect your session beans.

Don’t confuse EJB types

This may seem like a no-brainer, but don’t try to make one EJB type behave like another. If you’ve been paying attention throughout this book (you have, haven’t you?), the differences between EJB types should be pretty clear in your head. Session and message-driven beans manage processes (synchronously and asynchronously, respectively), entity beans persist data, and everyone is happy. That’s great! There are two possible wrinkles:

  • Not everyone has read this book; some people will have different understandings of how to design EJBs.

  • Your application will evolve, and the changes may alter your EJB design.

As a consequence, you may find some of the following in your application:

  • A custom JMS listener that calls a session bean.

  • Session beans presenting getters/setters for individual data items.

  • Entity beans containing complex business logic.[65]

These are all bad things.[66] If you see these or any similar misconceptions about what each kind of EJB does, do everything you can to fix them ASAP. Depending on the exact circumstances, the consequences may be minor—an additional class or two requiring creation and maintenance—or they may make the EJB nonfunctional or impossible to maintain.

Special Circumstances

Any application will have features that are best implemented by combining two or more technologies. We’ll look at several scenarios where EJB technology may need to be combined with other technologies and give some approaches to melding them successfully.

Returning entity data from EJBs

In all but the simplest applications, you will need to return data from your EJBs. This data will be used by other components, other tiers of the application, and maybe even other systems. While EJBs, specifically entity beans, could fulfill this need, there are several reasons why entity beans should not be used outside the EJB container (see the “Local versus remote interfaces” section, above).

The Transfer Object pattern is one solution to this problem. It provides lightweight objects specifically for sending data outside the EJB container. We can also use some other approaches to represent an entity’s data, such as an array of Strings, a Map of field-value pairs, a JDBC ResultSet, or XML. These approaches are generally more loosely coupled than Transfer Objects, providing greater flexibility with commensurate costs. Here’s a quick summary of the pros and cons of each approach:

Transfer objects

The available data is set in code, which makes for a strict interface. The remote client must be Java-based.

Array of Strings

There is no metadata, so the data order of the array must be known ahead of time, which makes for a unintuitive interface. The remote client does not need to be Java-based.

Map of field-value pairs

The field names in the Map provide some metadata, so data ordering is not a constraint. No type information is provided, so that must be specified or not needed. (For example, by assuming everything is a string.) This may handicap the interface for some complex business taskflows, but it is sufficient for most situations. The remote client must be Java-based.

JDBC ResultSet

This provides full metadata, making it useful for even the most complex taskflows. On the downside, the remote client must be Java-based.

XML

XML can express multiple levels of metadata and relationship complexity. Thus, it can be equivalent to the Map approach, equivalent to the ResultSet approach, or even express a complete entity hierarchy. The remote client does not need to be Java-based, but XML imposes some performance penalties. It is not very size-efficient, resulting in higher memory usage and slower network transmission, and it must be parsed to be programmatically accessed.

You have lots of freedom in how you choose to implement these approaches. For example, you could implement a Map-like structure using arrays of Strings to get the benefits of the former while remaining platform-agnostic. However, the benefits of that approach may be offset by the effort needed to build it.

One drawback to these approaches is that the data is a snapshot. If the underlying data changes, none of these structures will know. Therefore, it’s possible that changes to the underlying data could render the data contained in these structures incorrect. This risk can be mitigated by the following data latency strategies:

  • Use the data only during a limited lifetime, say, during a single UI request. Then discard it.

  • Always validate your business preconditions and process inputs before executing a taskflow. Do not blindly execute business logic. That way, you can always ensure that the right thing happens.

  • Buy or build a caching framework that integrates with your EJB code or your EJB container.

Of course, all of these strategies have downsides. You will need to balance the trade-offs of entity beans, these “snapshot” approaches, and the above data latency strategies against your requirements.

Sequential processing with EJBs

Many applications, especially those focused on business operations, require sequential (“batch”) processing, such as for an end-of-day process. In these kinds of taskflows, a series of well-defined steps are executed, and many of these steps involve processing a collection of entities. For example, our travel agent application might have an end-of-day process wherein it iterates through all the Customers and generates an invoice record for any new Reservations. Another step might populate reporting tables in a database. There are many other possible steps.

EJB makes implementing these features both easier and more difficult. It is easier because of the transactional enforcement and the logical assignment of responsibilities. After all, the application will make multiple changes in each step (at least one to each entity), and wrapping the changes in a transaction might save us from having to keep track of which entities we’ve processed and which we haven’t. It makes sense to put any processing logic in one place, such as a session bean.

On the other hand, sequential processing can be challenging because of EJB’s performance and resource overhead, and the constraints of transaction enforcement (see the “Minimize transaction scope” section, above). The more steps involved in your taskflow, the more likely you will be to exceed your system’s capabilities. The same is true as more EJBs (entity or session) are used in the taskflow: each additional EJB slows the processing that much more, perhaps unacceptably. Additionally, a gargantuan transaction that takes a long time to complete can have extreme concurrency ramifications.

The bottom line is EJB alone will probably not be successful here. A framework must be developed that incorporates EJB but is not limited by EJB. The heart of the framework is a process controller that knows how to execute a series of steps, each in its own transaction. Part of the process controller is implemented as a session bean. Then you can group the logical tasks of the business process into separate transactional steps. Each of these steps is implemented as a plain Java class that in turn calls the best feasible technology, and each class is called by the process controller. For example, aggregating reporting data in a database might be best implemented as a database stored procedure. Figure 19-9 illustrates a rough UML diagram of the framework.

Rough UML diagram of the framework

Figure 19-9. Rough UML diagram of the framework

In short, the sequential processing in your application will probably require some creative integration of EJB and other technologies. Be willing to explore different technologies to serve your needs.

Exceptions

Exceptions are fundamental to error notification and management in Java. Understanding exceptions and how to handle them is even more important in EJB because exceptions have a significant effect on transaction control. Be sure to review the section on exceptions and transactions in Chapter 16.

Exception design for EJBs is essentially the same as general exception design. The most noticeable difference is that EJB distinguishes between application and system exceptions rather than checked and unchecked exceptions. System exceptions are the same as unchecked exceptions (java.lang.RuntimeException and its subclasses), and application exceptions are checked exceptions—with one exclusion. That exclusion is java.rmi.RemoteException and its subclasses, which is used to indicate an underlying problem with a remote EJB call, such as a communication failure. As such, RemoteException appears in each EJB method in the interface, but not in the corresponding implementation.

There is an informal category of checked exceptions that deserve special treatment. We call them subsystem exceptions. As the name indicates, subsystem exceptions are checked exceptions thrown by a subsystem of the JVM or a resource, such as JDBC or JMS. For example, IOException is thrown by the I/O subsystem; JMSException is thrown by JMS; SQLException is thrown by JDBC, and so on. When one EJB calls another (by its remote interface), you treat RemoteException as a subsystem exception.

Here are the fundamental steps in exception design:

  1. Determine what application exceptions are needed.

  2. Design an exception hierarchy for the application exceptions.

  3. Wrap subsystem exceptions.

  4. Everything else will be system exceptions.

Identifying application exceptions

The first step is to determine the application exceptions. Application exceptions encapsulate business errors that prevent the completion of a taskflow. The user should be notified, or the application should attempt to recover from the error, or both. The essential criterion is that the error needs to be propagated several layers (at least) up the application call stack. For example, the Titan application would throw an application exception if a reservation could not be completed because the desired cruise was sold out, and this exception would cause the user interface to display an error message. Avoid scenarios where application exceptions are used as costly if-then statements or other forms of flow control. Exceptions are exceptional.[67]

Application exceptions can often be identified almost straight from your business requirements, so if the requirements are fully defined, much of the work in this step is already done. The trick is to make sure your exceptions focus on error conditions. Some developers have used exceptions for user interface control, which is bad. For example, if a query for cabin information from the Titan application had no results, it is better to return an empty Collection than throw an exception. Exceptions should be reserved for errors, and other mechanisms should be employed for controlling user interaction.

Design the exception hierarchy

After you have determined what application exceptions you need, incorporate them into a class hierarchy. A hierarchy provides at least two benefits:

  • Common functionality can be implemented in superclasses.

  • A package-specific superclass can be used in throws clauses instead of listing multiple subclasses. For example, the signature can show InventoryException instead of CabinSoldOutException, DeckSoldOutException, and CruiseSoldOutException.

Here are some specific steps to assist in creating the hierarchy:

  1. Always have a base class, probably abstract, to contain general exception functionality. This can be called AbstractException.

  2. AbstractException should also contain code and attributes for passing at least two error codes: one for user notification and another for developer notification. The codes should correspond to entries in a resource bundle or other text localization mechanism. Short, mnemonic textual codes (“AVAILABLE_INVENTORY_EXCEEDED”) rather than numeric or otherwise cryptic codes (“I-01765”) are preferable.

  3. Create a subclass of AbstractException for each major package, e.g., InventoryException, GuestException.

  4. Package-specific exceptions can be subclassed as necessary to indicate particular error conditions. As mentioned above, CabinSoldOutException, DeckSoldOutException, and CruiseSoldOutException are possible subclasses of InventoryException. Use as many subclasses as you need.

  5. When designing the EJB interfaces, start out by listing all exceptions that each method can throw. A rule of thumb is that if three or more exceptions thrown by a method are subclasses of the same package-level exception, replace them with the package-level exception.

Wrap subsystem exceptions

Subsystem exceptions should not appear in your EJB method signatures. The EJB interface presents functionality and data from a business perspective, while subsystems are implementation-specific. If you cannot recover from a subsystem exception inside your EJB, always catch and rethrow it wrapped in an EJBException.

try {
   ...
} catch ( SQLException se ) {
   throw new EJBException("SQLException caught during processing: " +
                              se.getMessage( ), se);
} catch ( RemoteException re ) {
   throw new EJBException("RemoteException caught during processing: "
                              + re.getMessage( ), re);
}

Base and Utility Classes

As you design your EJBs, you will begin to spot areas of common functionality. For example, since several classes and functions deal with reservations in the Titan application, several of the implementations may require the use of startDate and endDate parameters. They may even be of similar type (i.e., java.util.Date or something similar). As another example, suppose the DBA for your application’s database decides that there will be a timestamp column named LAST_MODIFIED in all database tables. Every single entity bean in your application will support this field. Furthermore, the implementation of this field will have to remain consistent across all implementations of all entity beans in order to be of use.

As a final example, consider the EJB implementation interfaces javax.ejb.EntityBean, javax.ejb.SessionBean, and javax.ejb.MessageDrivenBean. All require that our EJBs implement various container callback methods, regardless of whether they are actually implemented or not. For example, stateless session beans require but do not use ejbActivate( ) and ejbPassivate( ).

Each one of these situations adds some amount of code to every EJB; code which must be written and maintained. To avoid this development overhead, consider implementing this functionality in either base classes or utility classes, as appropriate. While it might not make sense to build a unique base class for two separate EJBs, if you have 10, suddenly the time investment in building the base class more than pays for itself.

As discussed here, a base class is generally declared abstract and inherited by your EJBs. They implement methods needed by all or several EJBs. A utility class implements generalized, frequently used structures or functionality. Utility classes are often used across several packages and really can’t be assigned to a specific domain area.

Base classes

We will create base classes for our EJBs that contain empty implementations of the container callback methods as well as methods for getting and setting the EJB context. Since the specific set of callback methods and the EJB context class depend on the type of EJB, we will create three base classes: AbstractEntityBean, AbstractSessionBean, and AbstractMessageDrivenBean. In addition, we will add support for the LAST_MODIFIED timestamp column, as it is a common feature in EJB applications. This step requires that we incorporate two abstract methods (getLastModified( ) and setLastModified( )) into the AbstractEntityBean class.

This is the code for AbstractEntityBean:

package com.titan.common;

import javax.ejb.EntityContext;
import javax.ejb.EntityBean;
import java.sql.Timestamp;

public abstract class AbstractEntityBean implements EntityBean {

   private EntityContext entityContext = null;

   public void setEntityContext(EntityContext context) {
      entityContext = context;
   }

   public EntityContext getEntityContext( ) {
      if ( null == entityContext ) {
         throw new IllegalStateException("The entity context has " +
                                         "not been set.");
      }
      return entityContext;
   }

   public void unsetEntityContext( ) {
      entityContext = null;
   }

   public void ejbActivate( ) {
   }

   public void ejbPassivate( ) {
   }

   public void ejbLoad( ) {
   }

   public void ejbStore( ) {
   }

   public void ejbRemove( ) {
   }

   public abstract Timestamp getLastModified( );

   public abstract void setLastModified(Timestamp lastModified);

}

This is the code for AbstractSessionBean:

package com.titan.common;

import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public abstract class AbstractSessionBean implements SessionBean {

   private SessionContext sessionContext;

   public void setSessionContext(SessionContext context) {
      sessionContext = context;
   }

   public SessionContext getSessionContext( ) {
      if ( null == sessionContext ) {
         throw new IllegalStateException("The session context has " +
                                         "not been set.");
      }

      return sessionContext;
   }

   public void unsetSessionContext( ) {
      sessionContext = null;
   }

   public void ejbActivate( ) {
   }

   public void ejbPassivate( ) {
   }

   public void ejbCreate( ) {
   }

   public void ejbRemove( ) {
   }
}

And here’s the code for AbstractMessageDrivenBean:

package com.titan.common;

import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;

public abstract class AbstractMessageDrivenBean implements MessageDrivenBean {

   private MessageDrivenContext messageContext;

   public void setMessageDrivenContext(MessageDrivenContext context) {
      messageContext = context;
   }

   public MessageDrivenContext getMessageDrivenContext( ) {
      if ( null == messageContext ) {
         throw new IllegalStateException("The message context has " +
                                         "not been set.");
      }

      return messageContext;
   }

   public void unsetMessageDrivenContext( ) {
      messageContext = null;
   }

   public void ejbCreate( ) {
   }

   public void ejbRemove( ) {
   }
}

Empty implementations of ejbCreate( ) have been provided for the session and message-driven bean base classes. We did not provide an ejbCreate( ) for the entity bean base class because each entity bean’s ejbCreate( ) must return that bean’s primary key type.

Our bean implementation classes will now extend the appropriate base class, like so:

public abstract class CabinBean extends AbstractEntityBean {
    ...
}

public class ReservationProcessorBean extends AbstractMessageDrivenBean 
implements javax.jms.MessageListener {
    ...
}

public class TravelAgentBean extends AbstractSessionBean {
    ...
}

MDBs must still implement the javax.jms.MessageListener interface.

With these changes, we have decreased the amount of code we have to write and maintain. During the implementation phase, you will probably find additional code that can be moved into the base classes.

Using base classes in this way presents three pitfalls:

  • If you only have a few EJBs, you will spend more time creating and using the base classes than you will save.

  • You will gain no benefit if you have to override more than a few methods in the base classes. This is especially likely with the container callback methods when you are creating stateful session beans.

  • You will not be able to inherit functionality from another class. If you need to do so, you will have to decide whether to copy the base class methods to your EJB or to access the other class’s functionality in another manner, such as composition.[68]

Utility classes

Now that we have taken care of the base classes, let’s turn to the utility classes. Utility classes are hard to define precisely, because they include generalized data-holding classes, such as a DateRange class that encapsulates a start date and an end date, and non-data classes that contain infrastructure-related, library-like, and convenience methods. Examples of non-data classes include a StringUtils class containing String manipulation functionality, an ObjectUtils class containing various equality and comparison convenience methods, or a DatabaseUtils class containing primary key generation and database connection functionality. Data-holding classes can be more ambiguous. Determining whether they are utility classes or domain-specific types will depend on your particular application and design. For example, a Money class that combines an amount and a currency could be considered a generalized, cross-package class or a finance-specific class.

The primary benefit of utility classes is reducing code duplication, which makes it easier to fix or improve your application without risking shotgun surgery.[69] Utility classes can also increase code readability.

You will discover candidate utility classes as you implement your design. The biggest sign that you might need a utility class is code duplication. If your code performs the same or very similar logic multiple times, or if two or more classes always accompany each other in methods or method signatures, you have a possible utility class (more correctly, a possible utility method or a possible utility class). Here’s a method that might belong in a utility class:

   public static boolean isEmpty(String str) {
      return ((str == null) || (str.trim( ).equals("")));
   }

The isEmpty method is very simple, but implementing it in a utility class is worthwhile if you check for null or empty Strings often enough—say, when validating method arguments. I would put this method in a StringUtils class.

Here’s an example of a data-holding utility class: suppose you have a series of classes with method signatures that require both a currency and an amount parameter every time:

public Ticket bookPassage(CreditCard card, double price, Integer currency)

If you created a Money class, the modified method from the TravelAgent session bean would look like this:

public Ticket bookPassage(CreditCard card, Money amount)

A DateRange utility class is a common requirement in handling reservations. For example, say that we had added startDate and endDate virtual persistence fields to the Cruise EJB:

public Date getStartDate( );
public void setStartDate(Date start);

public Date getEndDate( );
public void setEndDate(Date end);

Because travel agents will want to search for cruises by these fields, we have added a listMatchingCruises method to the TravelAgent EJB:

public Collection listMatchingCruises(Date start, Date end) throws RemoteException;

After a DateRange class is created, this method changes to:

public Collection listMatchingCruises(DateRange range) throws RemoteException;

While reduced duplication is the most obvious benefit to implementing utility classes, an additional benefit is that reduced duplication makes the interface more coherent: it’s easier to understand a method signature with a date range than a method signature with separate parameters for the start and end dates. Likewise, it’s easier to understand a Money parameter than separate price and currency parameters. Everyone who touches the revised bookPassage and listMatchingCruises methods—their developers, the developers of any client code, or some college intern tasked with maintaining the code a year or two down the line—will have a more intuitive grasp of what those methods expect.

Unfortunately, knowing when to implement this type of refactoring comes with experience. Fortunately, there is an excellent book on refactoring: Martin Fowler’s Refactoring: Improving the Design of Existing Code (Addison-Wesley). Take a look for other ways to identify candidates for utility classes (and for other ways to refactor your code).



[58] Business entity identification is part of a complete functional analysis. There is a great deal more involved in functional analysis for an application: user interface comps, lists of fields or attributes for each entity, and nonfunctional requirements (the number of users, usage patterns, and so on) are all examples of additional items you may need to include in a functional analysis in order to design the complete application.

[59] To extend the metaphor, the direct objects of the sentence will be the other entity beans (or possibly even session beans) that will be used by the session bean when it executes. This approach is the starting point from which we evolve the Session Façade design pattern, in which session beans encapsulate a taskflow that uses one or more components.

[60] Most developers would expect to see “deleted” instead of “inactivated,” but we have found that it is more prudent not to let the business tier delete configuration data (and possibly all application data). Instead, data should be deactivated by the business tier, and deleted only during archival or export to a data warehouse, according to an agreed upon process.

[61] EJB qualities such as object distribution and role-based security enforcement are irrelevant in this context, because the MDB has no connection to the message sender.

[62] There are four design patterns that will often be used in the design of EJB applications: Session Façade, Data Access Object, Transfer Object, and Business Delegate. We will not cover these in detail in this chapter. For more information, see the Design Patterns section of the Sun Microsystems site at http://java.sun.com/blueprints/corej2eepatterns/Patterns/.

[63] Remember that the transaction scope is propagated to all EJBs touched by the thread of execution, except for those EJB methods that have NotSupported or RequiresNew specified for their transaction attributes in the deployment descriptor.

[64] This kind of information is also necessary for accurate database sizing.

[65] We once saw a BMP entity bean designed to retrieve and manage a hierarchical collection, a tree, of key-value pairs. The entity bean contained data elements from the key table and the value table, all held in multiple instances of the same kind of entity bean. The entity bean contained the necessary logic to populate, traverse, and persist the entire tree of data.

[66] Can you identify the kinds of EJBs the examples should be? Hint: a message-driven bean, an entity bean, and a session bean.

[67] Because throwing exceptions is costly, your application should take reasonable steps to avoid predictable exceptions. In other words, be sure to check the preconditions at the beginning of all taskflows and methods. This also avoids performing part of a taskflow only to have to roll it back, which is a waste of time and resources. For example, check if the cruise is sold out before attempting to create a reservation. While the cruise might sell out in the split second between the check and the creation, it’s unlikely 99% of the time.

[68] I have never seen a case where an EJB should subclass a class other than with a base class, and a requirement like that is suspicious.

[69] Shotgun surgery takes place “...when every time you make a kind of change, you have to make a lot of little changes to a lot of different classes.” (From Refactoring: Improving the Design of Existing Code by Martin Fowler, published by Addison-Wesley.)

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.35.255