Design




Design

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

To discuss design, we need to change our thinking a bit. Throughout this book, we have focused on the details of EJBs and entity beans, and on 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 instead of 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 shortly):

  • 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 acting as clearinghouses 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 users. Connectivity to the external travel agencies and to the ship provisioning vendors is not guaranteed, so the communications mechanism will need to handle disconnects. Finally, we want to generate reservation confirmations and other forms in PDF format. Figure is a system diagram of our requirements so far.

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 that the application needs to represent. This is generally a lengthy process, and we will go over only some of the results here. Although in our example, this is presented as a systematic, one-time process, it is really iterative. You will probably take a first stab at identifying business entities and then go through the process repeatedly before having a final list.

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 matching 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, and simpler pieces of information (date period, subtotal) are not.


Kind of, belongs, has, and 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, and 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.

Because we focus on the components that will end up being EJBs, our functional analysis is completeselecting business entities is the most important part of the EJB design process for us.[*]

[*] Business entity identification is part of a complete functional analysis. A great deal more is 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 examples of additional items you may need to include in a functional analysis in order to design the complete application.

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 minute 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 is a UML diagram of our business entities and their relationships.

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 objects to implement as EJBs and entity beans, and what types of EJBs to use. But first, it will help to understand the technical architecture of the system because aspects of it will have direct implications on our entity implementation choices.

Technical Architecture

Earlier, we depicted our system in a high-level diagram (Figure). This diagram depicts relationships between 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?

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

  • 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).

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

    1. Reserve a Cabin for use by a Customer.

    2. Reduce the total number of Cabins by one.

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

  • 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 reservation 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.

  • 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.

  • 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.

Amended system diagramUML diagram of the application's business entities


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

EJB and Entity Bean Identification

Not all of our business entities will turn out to be EJBs or entity beans, 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 and entities in the application's technical architecture presents ambiguity and conflicting requirements. It's not easy to make the right choices. Fortunately, several rules of thumb will help guide the process.

Let's quickly review the component 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 as well as interacting with persistence units. Session beans represent taskflows. They are invoked locally or via RMI, both of which are 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 all of the components in the diagram are candidates for implementation as entity beans. Although it is tempting to use straight JDBC for pieces of data that are read-only, it is strongly suggested that you don't. Since you would probably be populating a Java object anyway from the result set of any JDBC invocation, why not let Java Persistence do the work for you? More importantly, though, most persistence providers supply nice caching layers so that frequently accessed data remains in memory and you can avoid making expensive network calls to your database.

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. A good starting place is reviewing the responsibilities depicted in the class diagram.

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

  • Views Cruises

  • Creates and updates Reservations

  • Creates and updates Customers

In any application, functionality seems to collect around one or more entities. Such a grouping of responsibilities often indicates that a session bean is needed, which brings us to the next guideline:


Guideline #2

Each session bean encapsulates access to data spanning concepts that are identified in the functional requirements analysis and initial technical architecture. So, when we see the grouping, 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. Instead it indicates where a session bean is needed. The session bean represents the action the entity takes, not the entity itself. Think of the entityimplemented as an entity beanas the subject of a sentence and the session bean as the sentence's verb.[*]

[*] 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.

As for the name for the session bean, a good tactic 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 methods 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 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 cases, the TravelAgent will list Cruises only 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 #3:


Guideline #3

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, that 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. Although 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. Because no taskflows are detailed at this point, 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 [*]

We can now expand our entity diagram into the class diagram shown in Figure.

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 they can be invoked asynchronously. Roughly put, they are transactional message handlers.[*] This suggests Guideline #4:

[*] 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.


Guideline #4

Each message-driven bean encapsulates related functionality that must be invoked in a transactional manner when an asynchronous message is received. So, as with session beans, in order to tell where we might want to use message-driven 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. Messaging takes place between our system and another (ostensibly external) system in two places:

  • 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 communicate only 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 reservations 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. Adding another method to a session bean does not introduce any complexity to the bean; it's just another method. However, in JMS-based MDBs, 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 one 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.

The Naming of MDBs

MDB names, like session bean names, should suggest what the component does. A rule of thumb is to combine a description of the kinds of messages that the MDB receives with the word Listener. Processor is a common alternative to Listener, but it is less definitive and thus easier to confuse.


While we're covering JMS-based MDBs, 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, its data is already structured for easy access by the MDB, whereas all but the simplest data 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 universal message typeevery messaging system knows how to send and receive simple text (and binary data). With that said, you should investigate message types and their tradeoffs 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. Although it requires a heavy XML parser when processing messages, TextMessage provides interoperability benefits that fit our needs.

We've now identified all of the EJBs and entity beans in our sample application. Figure shows an updated class diagram.

Updated class diagram


EJB Details

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

Stateless versus stateful session beans

As their names indicate, the difference between the two subtypes 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, 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 to presentation tiers of your application. Session bean state is a way of sharing information among 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 (see Figure).

Stateful version of ReservationManager


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

Stateless version of ReservationManager


The stateful session bean is more elegant when we need to call bookReservation, updateReservation, or cancelReservation multiple times, especially if you are using some of the extended persistence context queuing and batching features we discussed in Chapter 16.

However, there are certain performance considerations to think about. A stateful session bean must be created for each client that needs one. This stateful bean instance stays in memory until it is removed by its client or is passivated by the EJB container. This is probably not an issue in most applications, but if you have tens of thousands of stateful bean clients, it may become an issue. The side effects of using a stateful bean in this scenario can be minimized by making sure clients release their stateful bean references when they don't need them anymore, and by tuning the passivation timeout of your EJB container.

There are also performance considerations when you are clustering your stateful session beans. Clustered stateful session beans must have their state replicated to other nodes in the cluster so that client requests can successfully failover if a machine goes down. This can create a lot of network traffic and CPU cycles. In general, distributed systems should be designed to be as stateless as possible. However, if a client needs to have a stateful conversation with your application, then you're going to have to pay the cost of state replication somewhere in your application, whether it is in the web tier or in your EJB tier.

Local versus remote interfaces

Don't use remote interfaces unless you really have towe can't emphasize this 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 RemoteException s 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 they 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 with common sense:

  • 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.

How do we pass entity data, such as cruise information, across the remote interface? Good answers to this question are provided in the upcoming section "Returning entity data from EJBs." Because entity beans are plain Java objects, they can be serialized to remote clients. Entity beans can be a part of the interface of an EJB component.

It is a good idea to implement both remote and local interfaces for our session beans. While this results 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.

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

Entity Details

We may have finished identifying our entity beans and the relationships between them, but we still need to make a few design decisions regarding the persistence layer. One thing that needs consideration is how you transfer entity data from your session beans to your client code. Another thing that requires careful planning is how you interact with your data model. Database access usually has the most impact on the performance and scalability of your system. We'll discuss both of these issues in this section.

Returning entity data from EJBs

If you have remote clients that access your EJBs, you need to figure out how you will transfer entity information to and from the server. Because entity beans are plain Java objects, this job can be as simple as having your entity classes implement the java.io.Serializable or Externalizable interface and passing them as parameters in the remote interface of your EJBs:

@Entity
public Customer implements java.io.Serializable {
 ...
}

@Remote
public interface TravelAgentRemote {
   Ticket makeReservation(Reservation reservation);

   Reservation findReservation(Customer cust, Cruise cruise);

   void updateReservation(Reservation res);
}

For example, let's say you have a Swing GUI application that interacts with remote EJBs. It could create Reservation objects locally and send them to the server to be processed. The session bean could take the Reservation object as is and only call EntityManager.persist( ). Later, the Swing GUI might want to update this reservation. It would call the findReservation( ) method mentioned earlier, and it would display the Reservation on the GUI locally. Edits could be made directly to the Reservation instance on the client, sent back to the server, and merged with the EntityManager.merge( ) operation.

There is one pitfall to this kind of design, though. An entity bean may have many nested relationships that create a complex object graph. You don't want to serialize entire object graphs over the wire to your remote clients because they may be too large. Therefore, it is good practice to set the javax.persistence.FetchType of all your relationships to LAZY so that they are lazily loaded on demand on the server. In your server logic that obtains and returns references to these complex entities, you can choose which information you want to be loadedbased on the business process you are performingby using features such as JOIN FETCH queries.

Minimizing database access

The biggest bottleneck in EJB applications is usually the database. It is usually a highly contended resource that requires the most optimizations. Why is this so? First, the database is quite likely in another process on the same machine as the application server or, more likely, on another machine in the network. We mentioned earlier (in the local versus remote interface debate) that network invocations are expensive. This is also true of database calls. Second, the databases have to synchronize to disk any updates that you perform. A disk can go only so fast and is a resource that can be highly contended. Hard drives have a maximum throughput, and once you reach this limit, you can't go much further to increase the performance of your system. Therefore, you want to minimize database interaction as much as possible in your application design, which means limiting the number of round trips to the database and even combining multiple updates and/or queries into single operations. There are a couple of portable ways to tune your database access that we'll discuss in this section: caching and minimizing database invocations.

Caching

Caching will probably have the biggest impact on performance for your system. Caching is the act of leaving data in memory so that when a business process needs access to a certain entity bean, the persistence provider does not have to communicate to the database to obtain this information. Although caching is not discussed in the Java Persistence specification, all vendors will at least have some sort of single VM global cache that can hold data in memory until it is needed. Most vendors will additionally have distributed caching available so that you can retain this performance benefit when you run your application in a cluster of networked computers. Caching usually works with only the find( ) and getreference( ) EntityManager methods because the cache usually comprises only simple maps, with the primary key as the identifier. EJB QL queries are usually never cached, but some vendors do provide some ability in this area as well. It is a good idea to check your vendor's caching capabilities before making any purchasing decisions.

One thing to remember about caching is that one size does not fit all of the entity beans in your system. Although you can have a very large heap size with 64-bit systems nowadays, memory is still a finite resource. Some entities in your system may be accessed very rarely, and they may be using space that is needed by frequently accessed entities. In our Titan Reservation system, Ships, Cruises, and Cabins are f entities, and Customers and Reservations are rarely accessed after the initial order is placed in the system. In this case, it might make sense to eliminate or reduce the cache size for Customers and Reservations so that other entities that need caching can take up more room in memory. Another type of entity that you may not want to cache is one that is updated frequently. In this case, the cache is continuously being updated. While this isn't so important on applications that run on a single server, it can have performance penalties in a cluster because the updating server must send network messages to other nodes in the system to ensure that these remote caches are up-to-date.

So, some good common-sense rules for caching are:

  • Create large cache sizes for frequently accessed entities.

  • Create smaller caches or have no cache at all for infrequently accessed entities.

  • Consider not caching frequently updated entities.

Which entities you should cache and how large your cache should be are factors that depend on the size of your database and the amount of memory you have available on your servers. It is best to create real-world simulations of the traffic your system will receive to fine-tune your cache settings. Don't make caching decisions blindly.

Combining queries

Even if you have optimal cache settings, you can still do a lot to minimize the number of times you hit the database. One thing you can do is to combine queries. In the earlier section titled "Returning entity data from EJBs," we discussed how it is good practice to set the FetchType of your related entities to be LAZY so that you don't pull entire object graphs. A lazy fetch mode may have the side effect of forcing the underlying persistence provider to make multiple extra queries to traverse a relationship, when this relationship could have been fetched along with the root entity. For example, let's look at the @OneToMany Cruise-Reservation relationship:

1 Cruise cruise = entityManager.find(Cruise.class, 1) ;
2 ... // do some work on base cruise
3 Collection<Reservation> reservations = cruise.getReservations( );
4 for (Reservation res : reservations ) "
5 {
6    res.setDate(newDate);
7 }

If our @OneToMany Cruise-Reservation were set to LAZY fetching, then the preceding business process would result in two separate database queries. Line 1 would load the base cruise object. Line 3 would execute a separate query to pull in the reservations associated with the cruise. It is more optimal to pull in the reservations by doing a join when the cruise is first loaded to eliminate the extra queries. Your first reaction might be to change the FetchType to EAGER for this relationship. This is not a good choice because different business processes might not be interested in the reservations relationship. A better solution is to use the JOIN FETCH feature of EJB QL:

1 Query query = entityManager.createQuery("FROM Cruise c LEFT FETCH JOIN 
 c.reservations WHERE c.id = 1");
2 Cruise cruise = (Cruise)query.getSingleResult( );
3 ... // do some work on base cruise
4 Collection<Reservation> reservations = cruise.getReservations( );
5 for (Reservation res : reservations )
6 {
7   res.setDate(newDate);
8 }

In this code, even though the Reservation side of the relationship is marked as LAZY, the LEFT FETCH JOIN causes the relationship to be loaded when the cruise is queried.

Minimizing updates

Certain circumstances in your code can pop up, causing additional, unneeded database updates within a transaction. Because improving performance is mostly about reducing database access, you should be aware of these circumstances. Consider the following transaction:

1 Reservation res = entityManager.find(Reservation.class, 1);
2 res.setAmountPaid(amount);
3 
4 Query query = entityManager.createQuery("FROM Reservation res WHERE
res.dateReserved > 'APR-06-2005'");
5 List list = query.getResultList( );
6 for (Object obj : list) {
7    Reservation reservation = (Reservation)obj;
8    Reservation.setDateReserved(today);
9 }

The default FlushModeType for an EntityManager and for a Query is AUTO . This means that Line 4 may cause a database update of the Reservation updated in Line 2. The persistence provider may not be able to figure out whether the update in Line 2 affects the executed query. Because Lines 6-9 update reservations again, the same reservation might be updated in the database twice. A solution to this problem is to set FlushModeType to COMMIT . This defers any database updates, inserts, and deletes until the transaction commits.

entityManager.setFlushMode(FlushModeType.COMMIT);

Reservation res = entityManager.find(Reservation.class, 1);
res.setAmountPaid(amount);

Query query = entityManager.createQuery("FROM Reservation res WHERE
res.dateReserved > 'APR-05-2006'");
List list = query.getResultList( );
for (Object obj : list) {
   Reservation reservation = (Reservation)obj;
   Reservation.setDateReserved(today);
}

In many cases, the persistence provider can do batch queries when COMMIT is used and send all these changes in one network call. Another solution might be to always perform your queries upfront before any updates are made to the queried entities.

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 levels. This is the same task you would perform for any application, so we will not cover it here. However, this stage can undo or compromise 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.

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, however:

  • Not everyone will have 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[*]

    [*] We once saw a BMP entity bean designed to retrieve and manage a hierarchical collectiona treeof 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.

These are all bad things.[] 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 minoran additional class or two requiring creation and maintenanceor they may make the EJB nonfunctional or impossible to maintain.

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

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.[]

    [] 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.

  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.[*] 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.

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

  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.

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. With EJBs, though, you need to be aware of what types of exceptions roll back a transaction. Any exception annotated with @ApplicationException (rollback=true) causes a transaction rollback. Any exception annotated with @ApplicationException(rollback=false) does not roll back the transaction. If the <application-exception> element is used within the EJB XML deployment descriptor, the same rules apply. If there is no @ApplicationException annotation, or no EJB XML deployment descriptor metadata that defines the transaction rollback behavior, any java.lang.RuntimeException or java.rmi.RemoteException class, or any exception class that extends them, automatically causes a rollback.

Although the EJB specification allows you to manually roll back a transaction with the EJBContext.setRollbackOnly( ) method, it is usually a best practice to let thrown exceptions determine the transaction's viability. This will shrink the number of try/catch blocks in your code base and will generally simplify exception handling. You must be thorough with your application of @ApplicationException, <application-exception>, and runtime or remote exceptions, however, to make sure that the behavior of your system remains consistent.

Here are the fundamental steps in exception design:

  1. Identify business exceptions.

  2. Design an exception hierarchy for business exceptions.

  3. Decide the rollback behavior for each business exception and use @ApplicationException and <application-exception> appropriately.

  4. Decide whether each business exception should be an unchecked or a checked exception.

  5. Declare or wrap subsystem or third-party exceptions that cause rollbacks.

Identifying business exceptions

The first step is to determine the business exceptions. Business 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 a business 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 business exceptions are used as costly if-then statements or other forms of flow control. Exceptions are exceptional.[*]

[*] 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.

Business 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 produced no results, it is better to return an empty Collection than to throw an exception. Exceptions should be reserved for errors, and other mechanisms should be employed for controlling user interaction.

Designing the exception hierarchy

Examine your object model and object interaction diagrams to pinpoint all business exceptions that need to be thrown. After you have determined what business exceptions you need, incorporate them into a class hierarchy. A hierarchy provides at least two benefits:

  • Common functionality can be implemented in superclasses.

  • You can use a package-specific superclass in throws clauses instead of listing multiple subclasses. For example, the signature can show InventoryException rather than 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 packagee.g., InventoryException, GuestException .

  4. Package-specific exceptions can be subclassed as necessary to indicate particular error conditions. As mentioned earlier, 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.

Deciding on rollback exceptions

Now that you have your exception hierarchy, walk through it to determine whether each exception will cause an automatic transaction rollback. Even if you are annotation averse, this is the time to swallow your pride and use @ApplicationException to identify this behavior. This is good practice because the application code may want to determine generically whether a rollback happened.

try {
   ejb.doOperation( );
} catch (com.titan.exceptions.AbstractException ex) {
   ApplicationException ae = ex.getAnnotation(ApplicationException.class);
   if (ae.rollback( ) == true) System.out.println("unrecoverable error");
   else // try and recover
}

If you are thorough and use @ApplicationException everywhere in your exception hierarchy, you can examine this annotation metadata at runtime, without knowing the concrete type of the exception, to determine whether the transaction was rolled back.

Don't forget to annotate Runtime or RemoteException s that do not roll back the transaction automatically with @ApplicationException!

Checked versus unchecked exceptions

In most cases, you will want to use unchecked exceptions for your exception hierarchy. With checked exceptions, a lot of unnecessary try/catch blocks turn up all over the place. Usually, when an exception is thrown, nothing can be done and the thread of execution needs to be aborted anyway. Use checked exceptions only when you want to force the calling code to handle the exception. Even though you will primarily use unchecked exceptions, it is a good idea to put the unchecked exception in the throws clause of your methods. Most IDEs have automatic try/catch block generation around invoked methods. If the calling code wants to catch a particular unchecked exception, the IDE automatically creates the correct catch block if the exception is in the throws clause of the called method.

There is an informal category of checked exceptions that deserves 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. These exceptions usually mean that the transaction needs to be rolled back, so you must take appropriate measures to handle them.

Wrap subsystem exceptions

Whether or not subsystem exceptions should appear in your EJB method signatures is circumstantial. If the client can recover or retry the business operation based on information available from the subsystem exception, then it might be a good idea to put it within the throws clause. For instance, if you are doing straight JDBC, you may run into deadlock exceptions when doing the JDBC operation. In this case, you will want the exception to be rolled back, but the client may want to retry the operation because the deadlock conditions may have been cleared up with the rollback. In this case, you can add SQLException to your method signature, but you must also declare it as an <application-exception> in your XML deployment descriptor:

<ejb-jar>
   <assembly-descriptor>
      <application-exception>
         <exception-class>java.sql.SQLException</exception-class>
         <rollback>true</rollback>
      </application-exception>
   </assembly-descriptor>
</ejb-jar>

If the subsystem exception is not recoverable or retryable, then don't put it in the throws clause of your methods. In this case, you should catch and wrap the exception in an EJBException or an appropriate business exception:

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);
}

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 a similar type (i.e., java.util.Date ). As another example, suppose the DBA for your application's database decides that there will be a timestamp column named L*_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.

You'll start to notice that a bunch of regular Java objects will begin to pop up in your design. These are called 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 nondata classes that contain infrastructure-related, library-like, and convenience methods. Examples of nondata classes include a StringUtils class containing String manipulation functionality, an ObjectUtils class containing various equality and comparison convenience methods, and 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.[*] Utility classes can also increase code readability.

[*] 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, published by Addison-Wesley.)

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 enoughfor instance, 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, imagine 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;

An additional benefit of reduced duplication is that it makes the interface more coherent; it's easier to understand a method signature with a date range than one 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 methodstheir developers, the developers of any client code, or some college intern tasked with maintaining the code a year or two down the linewill 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).