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.
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:
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 Agents create and update Reservations and view Cruise information. Travel Agents are a kind of Person.
A Customer is also a kind of Person. Customers have zero or more Reservations.
A Ship has zero or more Cabins and belongs to zero or more Cruises.
A Cruise has a Ship and a date period and is associated with zero or more Reservations.
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:
Business entities are capitalized, while simpler pieces of information (date period, subtotal) are not.
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).
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.
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.
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?
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.
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:
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.
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.
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 19-3.
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.
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:
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.
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.
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.
With these characteristics in mind, let’s start out by identifying entity beans in our application.
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 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.
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.
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.
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.
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]
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:
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.
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:
Cabin, Cruise, Customer, Person, Reservation, Ship, TravelAgent
ConfigurationManager, ReservationManager
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
RemoteException
s.
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.
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).
Contrast that with the stateless version of
ReservationManager
, in which the current
Customer
is a parameter for those methods (Figure 19-8).
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.
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.
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 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
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.
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.
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:
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.
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]
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.
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.
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.
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.
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 String
s, 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:
The available data is set in code, which makes for a strict interface. The remote client must be Java-based.
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.
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.
This provides full metadata, making it useful for even the most complex taskflows. On the downside, the remote client must be Java-based.
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.
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.
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 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:
Determine what application exceptions are needed.
Design an exception hierarchy for the application exceptions.
Wrap subsystem exceptions.
Everything else will be system 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.
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:
Always have a base class, probably abstract, to contain general
exception functionality. This can be called
AbstractException
.
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.
Create a subclass of AbstractException
for each
major package, e.g., InventoryException
,
GuestException
.
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.
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.
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); }
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.
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]
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.)