In addition to defining the interactions among entity beans and other resources (workflow), session beans have another substantial benefit: they improve performance. The performance gains from using session beans are related to the concept of granularity. Granularity describes the scope of a business component, or how much business territory the component covers. The scope of these beans is limited to a single concept and can impact only the data associated with that concept. Session beans represent large, coarse-grained components with a scope that covers several business concepts—all the business concepts or processes the bean needs in order to accomplish a task. In distributed business computing, you rely on fine-grained components like entity beans to ensure simple, uniform, reusable, and safe access to data. Coarse-grained business components like session beans capture the interactions of entities or business processes that span multiple entities so that they can be reused; in doing so, they also improve performance on both the client and the server. As a rule of thumb, client applications should do most of their work with coarse-grained components like session beans, with limited direct interaction with entity beans.
To understand how session beans improve performance, we must address the most common problems cited with distributed component systems: network traffic, latency, and resource consumption.
One of the biggest problems of distributed component systems is that
they generate a lot of network traffic. This is especially true
of component systems that rely solely on entity-type business
components, such as EJB’s EntityBean
component. Every method call on a remote reference begins a remote
method-invocation loop, which sends information from the stub to the
server and back to the stub. The loop requires data to be streamed to
and from the client, consuming bandwidth. The reservation system for
Titan Cruise Lines uses several entity beans (e.g., the Ship, Cabin,
Cruise, and Customer EJBs). As we navigate through these fine-grained
beans, requesting information, updating their states, and creating
new beans, we generate network traffic if we are accessing the beans
from remote clients. One client probably does not generate much
traffic, but if we multiply that traffic level by thousands of
clients, problems start to develop. Eventually, thousands of clients
will produce so much network traffic that the system as a whole will
suffer.
Another aspect of network communications is latency. Latency is the delay between the time at which we execute a command and the time at which it completes. With enterprise beans there is always a bit of latency due to the time it takes to communicate requests via the network. Each method invocation requires an RMI loop that takes time to travel from the client to the server and back to the client. A client that uses many beans will suffer from a time delay with each method invocation. Collectively, the latency delays can result in very slow clients that take several seconds to respond to each user action.
Accessing coarse-grained session beans from the client instead of
fine-grained entity beans from remote clients can substantially
reduce problems with network bandwidth and latency. In Chapter 12, we developed the
bookPassage()
method on the TravelAgent bean. The
bookPassage()
method encapsulates the interactions
of entity beans that would otherwise have resided on the client. For
the network cost of one method invocation on the client
(bookPassage()
), several tasks are performed on
the EJB server. Using session beans to encapsulate several tasks
reduces the number of remote method invocations needed to accomplish
a task, which reduces the amount of network traffic and latency
encountered while performing these tasks.
In EJB 2.0, a good design is to use remote component interfaces on the session bean that manages the workflow and local component interfaces on the enterprise beans (both entity and session) that it manages. This ensures the best performance.
We don’t want to abandon the use of entity business components, because they provide several advantages over traditional two-tier computing. They allow us to encapsulate the business logic and data of a business concept so that it can be used consistently and reused safely across applications. In short, entity business components are better for accessing business state because they simplify data access.
At the same time, we don’t want to overuse entity beans on remote clients. Instead, we want the client to interact with coarse-grained session beans that encapsulate the interactions of small-grained entity beans. There are situations in which the client application should interact with entity beans directly. If a remote client application needs to edit a specific entity—change the address of a customer, for example—exposing the client to the entity bean is more practical than using a session bean. If, however, a task needs to be performed that involves the interactions of more than one entity bean—transferring money from one account to another, for example—a session bean should be used.
When a client application needs to perform a specific operation on an entity, such as an update, it makes sense to make the entity available to the client directly. If the client is performing a task that spans business concepts or otherwise involves more than one entity, that task should be modeled in a session bean as a workflow. A good design will emphasize the use of coarse-grained session beans as workflow and will limit the number of activities that require direct client access to entity beans.
In EJB 2.0, entity beans that are accessed by both remote clients and local enterprise beans can accommodate both by implementing both remote and local component interfaces. The methods defined in the remote and local component interfaces do not need to be identical; each should define methods appropriate to the clients that will use them. For example, the remote interfaces might make more use of bulk accessors than the local interface.
Make decisions about whether to access
data directly or through entity beans with care. Listing behavior
that is specific to a workflow can be provided by direct data access
from a session bean. Methods like
listAvailableCabins()
in the TravelAgent bean use
direct data access because it is less expensive than creating a find
method in the Cabin bean that returns a list of Cabin beans. Every
bean that the system has to deal with requires resources; by avoiding
the use of components where their benefit is questionable, we can
improve the performance of the whole system. A
CTM is like a powerful truck, and each
business component it manages is like a small weight. A truck is much
better at hauling around a bunch of weights than an lightweight
vehicle like a bicycle, but piling too many weights on the truck will
make it just as ineffective as the bicycle. If neither vehicle can
move, which one is better?
Chapter 12 discussed the TravelAgent bean’s
listAvailableCabins()
method as an example of a
method that returns a list of tabular data. This section provides
several different strategies for implementing listing behavior in
your beans.
Tabular data is data that is arranged into rows and columns. Tabular data is often used to let application users select or inspect data in the system. Enterprise JavaBeans lets you use find methods to list entity beans, but this mechanism is not a silver bullet. In many circumstances, find methods that return remote references are a heavyweight solution to a lightweight problem. For example, Table 15-1 shows the schedule for a cruise.
Table 15-1. Hypothetical cruise schedule
Cruise ID |
Port-of-call |
Arrive |
Depart |
---|---|---|---|
233 |
San Juan |
June 4, 2002 |
June 5, 2002 |
233 |
Aruba |
June 7, 2002 |
June 8, 2002 |
233 |
Cartagena |
June 9, 2002 |
June 10, 2002 |
233 |
San Blas Islands |
June 11, 2002 |
June 12, 2002 |
It would be possible to create a Port-Of-Call
entity object that represents every destination and then obtain a
list of destinations using a find method, but this would be overkill.
Recognizing that the data is not shared and is useful only in this
one circumstance, we would rather present the data as a simple
tabular listing.
In this case, we will present the data to the bean client as an array
of String
objects, with the values separated by a
character delimiter. Here is the method signature used to obtain the
data:.
public interface Schedule implements javax.ejb.EJBObject { public String [] getSchedule(int ID) throws RemoteException; }
And here is the structure of the String
values
returned by the getSchedule()
method:
233; San Juan; June 4, 2002; June 5, 2002 233; Aruba; June 7, 2002; June 8, 2002 233; Cartegena; June 9, 2002; June 10, 2002 233; San Blas Islands; June 11, 2002; June 12, 2002
The data could also be returned as a multidimensional array of strings, in which each column represents one field. This would certainly make it easier to reference each data item, but would also complicate navigation.
One disadvantage of using the simple array strategy is that Java is
limited to single-type arrays. In other words, all the elements in
the array must be of the same type. We use an array of
String
s here because it has the most flexibility
for representing other data types. We could also have used an array
of Object
s or even a Vector
.
The problem with using an Object
array or a
Vector
is that there is no typing information at
runtime or development time.
Instead of returning a simple array, a method that implements some sort of listing behavior can return an array of structures. For example, the cruise ship schedule data illustrated in Table 15-1 could be returned as an array of schedule structures. The structures are simple Java objects with no behavior (i.e., no methods) that are passed in an array. The definitions of the structure and the bean interface that would be used are:
// Definition of the bean that uses the structure public interface Schedule implements javax.ejb.EJBObject { public CruiseScheduleItem [] getSchedule(int ID) throws RemoteException; } // Definition of the structure public class CruiseScheduleItem { public int cruiseID; public String portName; public java.util.Date arrival; public java.util.Date departure; }
Using structures allows the data elements to be of different types. In addition, the structures are self-describing: it is easy to determine the structure of the data in the tabular set based on its class definition.
A more sophisticated and flexible way to implement a list is to
provide a pass-by-value implementation of the
java.sql.ResultSet
interface.
Although it is defined in the JDBC package
(java.sql
), the ResultSet
interface is semantically independent of relational databases; it can
be used to represent any set of tabular data. Since the
ResultSet
interface is familiar to most enterprise
Java developers, it is an excellent construct for use in listing
behavior. Using the ResultSet
strategy, the
signature of the getSchedule()
method would be:
public interface Schedule implements javax.ejb.EJBObject { public ResultSet getSchedule(int cruiseID) throws RemoteException; }
In some cases, the tabular data displayed at the client may be
generated using standard SQL through a JDBC driver. If the
circumstances permit, you may choose to perform the query in a
session bean and return the result set directly to the client through
a listing method. However, there are many cases in which you
don’t want to return a ResultSet
that comes
directly from JDBC drivers. A ResultSet
from a
JDBC 1.x driver is normally connected directly to the database, which
increases network overhead and exposes your data source to the
client. In these cases, you can implement your own
ResultSet
object that uses arrays or vectors to
cache the data. JDBC 2.0 provides a cached
javax.sql.RowSet
that looks like a
ResultSet
but is passed by value and provides
features such as reverse scrolling. You can use the
RowSet
, but do not expose behavior that allows the
result set to be updated. Data updates should be performed only by
bean methods.
Sometimes the tabular data comes from several data sources or
nonrelational databases. In these cases, you can query the data using
the appropriate mechanisms within the listing bean and then reformat
the data into your ResultSet
implementation.
Regardless of the source of the data, you should still present it as
tabular data using a custom implementation of the
ResultSet
interface.
Using a ResultSet
has a number of advantages and
disadvantages. First, the advantages:
The ResultSet
interface provides a consistent
interface that developers are familiar with and that is consistent
across different listing behaviors. Developers do not need to learn
several different constructs for working with tabular data; they use
the same ResultSet
interface for all listing
methods.
The ResultSet
interface provides a consistent
interface that allows software algorithms to operate on data
independent of its content. You can create a builder that constructs
an HTML or GUI table based on any set of results that implements the
ResultSet
.
The ResultSet
interface defines several metadata
methods that provide developers with runtime information describing
the result set with which they are working.
The ResultSet
interface is independent of the data
content, which allows tabular sets to change their schemas
independently of the interfaces. A change in schema does not require
a change to the method signatures of the listing operations.
And now, the disadvantages of using a ResultSet
:
The ResultSet
interface strategy is much more
complex than returning a simple array or an array of structures. It
normally requires you to develop a custom implementation of the
ResultSet
interface. If properly designed, the
custom implementation can be reused across all your listing methods,
but it is still a significant development effort.
Although the ResultSet
can describe itself through
metadata at runtime, it cannot describe itself at development time.
Unlike a simple array or an array of structures, the
ResultSet
interface provides no clues at
development time about the structure of the underlying data. At
runtime, metadata is available, but at development time, good
documentation is required to express the structure of the
data
explicitly.