Using Enterprise and Entity Beans
Let's look at how a client would use an enterprise bean to do something useful. In this example, we'll create a TravelAgent session bean that creates a reservation for a remote client. This TravelAgent EJB will interact with various entity and other session bean types.
Modeling Taskflow with Session Beans
Entity beans are useful for representing data and describing business concepts that can be expressed as nouns, but they're not very good at representing a process or a task. A Ship bean provides methods and behavior for doing things directly to a ship,
but it does not define the context under which these actions are taken. We don't want business logic in the client applicationthat's why we went to a multitier architecture in the first place. Similarly, we don't want this kind of logic in our entity beans that represent ships and cabins. Booking passengers on a ship or scheduling a ship for a cruise is a type of activity or function of the business, not of the Ship or Cabin bean, and is therefore expressed in terms of a process or task.
Session beans act as agents that manage business processes or tasks for the client; they're the appropriate place for business logic. A session bean is not persistent; nothing in a session bean maps directly into a database or is stored between sessions. Session beans work with entity beans, data, and other resources to control taskflow
. Taskflow is the essence of any business system, because it expresses how entities interact to model the actual business. Session beans control tasks and resources but do not themselves represent data.
 |
The term taskflow was coined specifically for this book. It's derived from the term workflow, which is frequently used to describe the management of business processes that may span several days with lots of human intervention. In contrast to workflow, taskflow is used in this book to describe the interactions of beans within a single transaction that takes only a few seconds to execute. |
|
The following code demonstrates how a session bean designed to make cruise-line reservations might control the taskflow of other entity and session beans. Imagine that a piece of client software, in this case a user interface, obtains a remote reference to a TravelAgent session bean. Using the information entered into text fields by the user, the client application books a passenger on a cruise:
// Get the credit card number from the text field.
String creditCard = textField1.getText( );
int cabinID = Integer.parseInt(textField2.getText( ));
int cruiseID = Integer.parseInt(textField3.getText( ));
Customer customer = new Customer(name, address, phone);
// Create a new TravelAgent session, passing in a reference to a
// customer entity bean.
TravelAgentRemote travelAgent = ...; // Use JNDI to get a reference
travelAgent.setCustomer(customer);
// Set cabin and cruise IDs.
travelAgent.setCabinID(cabinID);
travelAgent.setCruiseID(cruiseID);
// Using the card number and price, book passage.
// This method returns a Reservation object.
Reservation res = travelAgent.bookPassage(creditCard, price);
We start by getting a remote reference to the TravelAgent EJB's remote interface. We need a remote reference rather than a local interface because the client is an application located outside the EJB container. It's not shown in the example, but references to remote session beans are obtained using JNDI.
JNDI is a powerful API for locating resources, such as remote objects, on networks. JNDI lookups are covered in subsequent chapters.
The rest of the client code interacts with the TravelAgent EJB to set the Customer, Cabin, and Cruise beans for the reservation desired. It then books the ticket by calling the bookPassage( ) method. It gets back a Reservation instance so that it can display it to its GUI to provide a receipt for the customer.
This is a fairly coarse-grained abstraction of the process of booking a passenger: most of the details are hidden from the client. Hiding the fine-grained details of taskflow is important because it provides the system with flexibility as it evolves: we know that we will always want to book passengers, but the process for booking a passenger may change.
 |
Coarse-grained and fine-grained are terms that are sometimes used to describe the level of detail exposed by the public interface of a component. A fine-grained component is one that exposes a lot of detail about how the component functions via its public interfaces. Components that provide a public interface but do not expose the details of its operation are called coarse-grained. When dealing with remote clients, coarse-grained interfaces are usually preferred because they are more flexiblethe client doesn't have to be aware of all the nitty-gritty details of how the component works. |
|
The following listing shows some of the code for the travelAgentBean. The bookPassage( ) method works with four entity beans, the Customer, Cabin, Cruise, and Reservation entities, and another session bean, the ProcessPayment EJB. The ProcessPayment EJB provides several methods for making a payment, including by check, cash, and credit card. In this case, we use the ProcessPayment bean to make a credit card payment. Once payment has been made, the Reservation created is detached automatically from persistence storage and is returned to the client.
@Stateful
public class TravelAgentBean implements TravelAgentRemote {
@PersistenceContext private EntityManager entityManager;
@EJB private ProcessPaymentRemote process;
private Customer customer;
private Cruise cruise;
private Cabin cabin;
public void setCustomer(Customer cust) {
entityManager.create(cust);
customer = cust;
}
public void setCabinID(int id) {
cabin = entityManager.find(Cabin.class, id);
}
public void setCruiseID(int id) {
cruise = entityManager.find(Cruise.class, id);
}
public Reservation bookPassage(String card, double price)
throws IncompleteConversationalState {
if (customer == null ||cruise == null ||cabin == null){
throw new IncompleteConversationalState( );
}
try {
Reservation reservation =
new Reservation(customer,cruise,cabin,price,new Date( ));
entityManager.persist(reservation);
process.byCredit(customer,card,price);
return reservation;
}catch(Exception e){
throw new EJBException(e);
}
}
}
There are a few things going on within this TravelAgent EJB. References to the EntityManager service and the ProcessPayment EJB are injected directly into the TravelAgent by the EJB container. The EJB container knows how to do this by the @javax.persistence.PersistenceContext
and @javax.ejb.EJB
annotations on the private fields of the session bean. We'll talk more about injection in Chapter 11 and we'll provide complete details in Chapter 14. Notice that the Customer entity is allocated on the client, passed to the server, and then created by the EntityManager. The setCabinID( ) and setCruiseID( )methods use the EntityManager to locate those beans in persistent storage. The bookPassage( ) method allocates a Reservation entity, inserts the reservation in the database by calling EntityManager.persist( ), and then bills the customer's credit card by invoking on the ProcessPayment EJB. Finally, when the bookPassage( ) method completes (and along with it, the transaction), the Reservation entity becomes unmanaged and detached from the EntityManager. It is returned as a plain Java object to the client.
This example leaves out some details, but it demonstrates the difference in purpose between a session bean and an entity bean. Entity beans represent the behavior and data of a business object, and session beans model the taskflow. The client application uses the TravelAgent EJB to perform a task using other beans. For example, the TravelAgent EJB uses a ProcessPayment EJB and a Reservation EJB in the process of booking passage. The ProcessPayment EJB processes the credit card, and the Reservation EJB records the actual reservation in the system. Session beans can also be used to read, update, and delete data that can't be adequately captured in an entity bean. Session beans don't represent records or data in the database, but they can access data.
Session beans allow clients to perform tasks without being concerned with the details that make up those tasks. A developer can update the session bean, possibly changing the taskflow, without affecting the client code. In addition, if the session bean is properly defined, other clients that perform the same tasks can reuse it. The ProcessPayment session bean, for example, can be used in many areas besides reservations, including retail and wholesale sales. For example, the ship's gift shop could use the ProcessPayment EJB to process purchases. As a client of the ProcessPayment EJB, the TravelAgent EJB doesn't care how ProcessPayment works; it's only interested in the ProcessPayment EJB's coarse-grained interface, which validates and records charges.
Moving taskflow logic into a session bean also simplifies the client application and reduces network traffic. Excessive network traffic is a common problem for distributed object systems: it can overwhelm the server and clog the network, hurting response time and performance. Session beans, if used properly, can reduce network traffic by limiting the number of requests needed to perform a task. The user of session beans keeps the interaction between the beans involved in a taskflow on the server. One method invocation on the client application results in many method invocations on the server, but the network sees only the traffic produced by the client's call to the session bean. In the TravelAgent EJB, the client invokes bookPassage( ); in turn, bookPassage( ) makes several method invocations on other enterprise beans. Furthermore, the TravelAgent bean may be in the same container as the other beans, and therefore can use the local interfaces, further reducing network traffic. For the network cost of one method invocation, the client gets several method invocations.
Stateful and statelessl session beans
Session beans can be either stateful or stateless. Stateful session beans maintain conversational state
when used by a client. Conversational state is not written to a database; it's information that is kept in memory while a client carries on a conversation with an enterprise bean, and it is lost when the conversation ends or the EJB container crashes. For example, a client making a reservation through the TravelAgent bean may call the methods that set cabin and cruise IDs. These IDs are part of the session's conversational state, and they affect the behavior of subsequent method calls, such as the call to bookPassage( ) that makes the actual reservation. Conversational state is kept only as long as the client application is actively using the bean. Once the client shuts down or releases the TravelAgent EJB, the conversational state is lost forever. Stateful session beans are not shared among clients; they are dedicated to the same client for the life of the enterprise bean.
Stateless session beans do not maintain any conversational state. Each method is completely independent and uses only data passed in its parameters. The ProcessPayment EJB is a perfect example of a stateless session bean: it doesn't need to maintain any conversational state from one method invocation to the next. All the information needed to make a payment is passed into the byCredit( ) method. Stateless session beans provide better performance and consume fewer resources than entity and stateful session beans because a few stateless session bean instances can serve hundreds and possibly thousands of clients. Chapter 11 talks more about stateless session beans.
Message-Driven Beans
Message-driven beans are integration points for other applications interested in working with EJB applications. Java applications or legacy systems that need to access EJB applications can send messages to message-driven beans
via JMS. These beans process those messages and perform the required tasks using other entity and session beans. EJB 3.0 is not limited to JMS-based message-driven beans: message-driven beans can support any messaging system that implements the correct JCA 1.5 contracts. However, support for JMS-based message-driven beans (JMS-MDBs)
in EJB 3.0 is mandatory, so JMS-MDBs are the type of message-driven bean addressed in this section.
In many ways, JMS-MDBs fulfill the same role as stateless session beans: they manage the taskflow of entity and session beans. The task is initiated by an asynchronous message sent by an application using JMS. Unlike session beans, which respond to business methods invoked on their component interfaces, a JMS-MDB responds to messages delivered through its onMessage( ) method. Since the messages are asynchronous, the client that sends them doesn't expect a reply. The messaging client simply sends the message and forgets about it.
As an example, we can recast the TravelAgent EJB developed earlier as the ReservationProcessor JMS message-driven bean:
@MessageDriven
public class ReservationProcessorBean implements javax.jms.MessageListener {
@PersistenceContext private EntityManager entityManager;
@EJB private ProcessPaymentRemote process;
public void onMessage(Message message) {
try {
MapMessage reservationMsg = (MapMessage)message;
Customer customer = (Customer)reservationMsg.getObject("Customer");
int cruisePk = reservationMsg.getInt("CruiseID");
int cabinPk = reservationMsg. getInt("CabinID");
double price = reservationMsg.getDouble("Price");
String card = reservationMsg.getString("card");
entityManager.persist(customer);
Cruise cruise = entityManager.find(Cruise.class, cruisePK);
Cabin cabin = entityManager.find(Cabin.class, cruisePK);
Reservation reservation =
new Reservation(customer,cruise,cabin,price,new Date( ));
entityManager.create(reservation);
process.byCredit(customer,card,price);
} catch(Exception e) {
throw new EJBException(e);
}
}
}
All the information about the reservation is obtained from the message delivered to the MDB. JMS messages can take many forms; the javax.jms.MapMessage
used in this example carries name-value pairs. Once the information is gathered from the message and the enterprise bean references are obtained, the reservation is processed in the same way as it was in the session bean. The only difference is that the Reservation object isn't returned to the sender of the message; message-driven beans don't have to respond to the sender.
Regardless of the messaging system, message-driven beans do not maintain any conversational state. Each new message is independent of the previous messages. The message-driven bean is explained in detail in Chapter 12.
 |