Passing objects by value is tricky with Enterprise JavaBeans. Two simple rules will keep you out of most problem areas: objects that are passed by value should be fine-grained dependent objects or wrappers used in bulk accessors, and dependent objects should be immutable.
The concept of dependent objects was addressed in Chapter 6, which describes the use of dependent objects in EJB 2.0. But for EJB 1.1, dependent objects are a new concept. EJB 2.0 and EJB 1.1 use dependent objects differently, because EJB 2.0 can accommodate much more fine-grained entity beans than EJB 1.1.
Dependent objects are objects that have meaning only within the
context of another business object. They typically represent fairly
fine-grained business concepts, such as an address, phone number, or
order item. For example, an address has little meaning when it is not
associated with a business object such as Person
or Organization
. It depends on the context of the
business object to give it meaning. Such an object can be thought of
as a wrapper for related data. The fields that make up an address
(street
, city
,
state
, and zip
) should be
packaged together in a single object called
AddressDO
. In turn, the
AddressDO
object is usually an attribute or
property of another business object; in EJB, we would typically see a
dependent object such as AddressDO
as a property
of an entity bean.
Here’s a typical implementation of an AddressDO object
:
public class AddressDO implements java.io.Serializable { private String street; private String city; private String state; private String zip; public Address(String str, String cty, String st, String zp) { street = str; city = cty; state = st; zip = zp; } public String getStreet() {return street;} public String getCity() {return city;} public String getState() {return state;} public String getZip() {return zip;} }
We want to make sure that clients do not change an
AddressDO
object’s fields. The reason is
quite simple: the AddressDO
object is a copy, not
a remote reference. Changes to an AddressDO
object
are not reflected in the entity from which it originated. If a client
changes the AddressDO
object, those changes will
not be reflected in the database. Making the
AddressDO
object immutable helps to ensure that
clients do not mistake this fine-grained object for a remote
reference, thinking that a change to an address property will be
reflected on the server.
To change an address, the client is required to remove the
AddressDO
object and add a new one with the
changes. Again, this is because the dependent object is not a remote
object and changes to its state are not reflected on the server. Here
is the remote interface to a hypothetical Employee bean that
aggregates address information:
public interface Employee extends javax.ejb.EJBObject { public AddressDO [] getAddresses() throws RemoteException; public void removeAddress(AddressDO adrs) throws RemoteException; public void addAddress(AddressDO adrs) throws RemoteException; // ... Other business methods follow. }
In this interface, the Employee can have many addresses, which are
obtained as a collection of pass-by-value
AddressDO
objects. To remove an address, the
target AddressDO
object is passed back to the bean
in the removeAddress()
method. The bean class then
removes the matching AddressDO
object from its
persistence fields. To add an address, an
AddressDO
object is passed to the bean by value.
Dependent objects may be persistence fields or they may be properties
that are created as needed. The following code demonstrates both
strategies using the AddressDO
object. In the
first listing, the AddressDO
object is a
persistence field, while in the second the
AddressDO
object is a property that does not
correspond to any single field; we create the
AddressDO
object as needed but do not save it as
part of the bean. Instead, the AddressDO
object
corresponds to four persistence fields: street
,
city
, state
, and
zip
.
// Address as a persistence field public class Person extends javax.ejb.EntityBean { public AddressDO address; public AddressDO getAddress() { return address; } public void setAddress(AddressDO addr) { address = addr; } ... } // Address as a property public class Person extends javax.ejb.EntityBean { public String street; public String city; public String state; public String zip; public AddressDO getAddress() { return new AddressDO(street, city, state, zip); } public void setAddress(AddressDO addr) { street = addr.street; city = addr.city; state = addr.state; zip = addr.zip; } ... }
When a dependent object is used as a property, it can be synchronized
with the persistence fields in the accessor methods themselves or in
the ejbLoad()
and ejbStore()
methods. Both strategies are acceptable.
This discussion of dependent objects has been full of generalizations and thus may not be applicable to all situations. That said, it is recommended that only very fine-grained, dependent, immutable objects should be passed by value. All other business concepts should be represented as entity or session beans. A very fine-grained object is one that has very little behavior, consisting mostly of get and set methods. A dependent object is one that has little meaning outside the context of its aggregator. An immutable object is one that provides only get methods and thus cannot be modified once created.
Dependent objects make excellent homes for format-validation rules. Format validation ensures that a simple data construct adheres to a predetermined structure or form. As an example, a Zip Code always has a certain format. It must be composed of digits; it must be five or nine digits in length; and if it has nine digits, it must use a hyphen as a separator between the fifth and sixth digits. Checking to see that a Zip Code follows these rules is format validation.
One problem that all developers face is deciding where to put validation code. Should data be validated at the user interface (UI), or should it be done by the bean that uses the data? Validating the data at the UI has the advantage of conserving network resources and improving performance. However, validating data in the bean, on the middle tier, ensures that the logic is reusable across user interfaces. Dependent objects provide a logical compromise that allows data to be validated on the client but remain independent of the UI. Since the validation logic is located in the constructor of the dependent object, the object automatically validates the data when it is created. When data is entered at the UI (e.g., GUI, servlet, or JSP), the UI can validate it using its corresponding dependent object. If the data is valid, the dependent object is created; if the data is invalid, the constructor throws an exception.
The following code shows a dependent object that represents a Zip Code. It adheres to the rules for a dependent object as I have defined them and also includes format-validation rules in the constructor:
public class ZipCodeDO implements java.io.Serializable { private String code; private String boxNumber; public ZipCode(String zipcode) throws ValidationException { if (zipcode == null) throw new ValidationException("Zip Code cannot be null"); else if (zipcode.length()==5 && ! isDigits(zipcode)) throw new ValidationException("Zip Code must be all digits"); else if (zipcode.length()==10 ) { if (zipcode.charAt(5) == '-' ) { code = zipcode.substring(0,5); if (isDigits( code )){ boxNumber = zipcode.substring(6); if (isDigits( boxNumber )) return; } } throw new ValidationException("Zip Code must be of form #####-####"); } } private boolean isDigits(String str) { for (int i = 0; i < str.length(); i++) { char chr = str.charAt(i); if ( ! Character.isDigit(chr)) { return false; } } return true; } public String getCode() { return code; } public String getBoxNumber() { return boxNumber; } public String toString() { return code+'-'+boxNumber; } }
This simple example illustrates that format validation can be
performed by dependent objects when the object is constructed at the
user interface or client. Any format-validation errors are reported
immediately, without any interaction with the middle tier of the
application. In addition, any business object that uses
ZipCodeDO
automatically gains the benefit of the
validation code, making the validation rules reusable (and
consistent) across beans. Placing format validation in the dependent
object is also a good coding practice because it makes the dependent
object responsible for its own validation; responsibility is a key
concept in object-oriented programming. Of course, dependent objects
are useful for validation only if the Enterprise JavaBeans
implementation supports pass-by-value.
As an alternative to using dependent objects, format validation can
be performed by the accessors of enterprise beans. If, for example, a
customer bean has accessors for setting and obtaining the Zip Code,
those accessors could incorporate the validation code. While this
strategy is more efficient from a network perspective—passing a
String
value is more efficient than passing a
dependent object by value—it is less reusable than housing
format-validation rules in dependent objects.
Most entity beans have several persistence fields that are manipulated through accessor methods. Unfortunately, the one-to-one nature of the accessor idiom can result in many invocations when accessing an entity, which translates into a lot of network traffic when using remote references. Every field you want to modify requires a method invocation, which in turn requires you to go out to the network. One way to reduce network traffic when accessing entities from remote clients is to use bulk accessors, which package access to several persistence fields into a single accessor method. Bulk accessors provide get and set methods that work with structures or simple pass-by-value objects. The following code shows how a bulk accessor could be implemented for the Cabin bean:
// CabinData DataObject public class CabinData { public String name; public int deckLevel; public int bedCount; public CabinData() { } public CabinData(String name, int deckLevel, int bedCount) { this.name = name; this.deckLevel = deckLevel; this.bedCount = bedCount; } } // CabinBean using bulk accessors public class CabinBean implements javax.ejb.EntityBean { public int id; public String name; public int deckLevel; public int ship; public int bedCount; // bulk accessors public CabinData getData() { return new CabinData(name,deckLevel,bedCount); } public void setData(CabinData data) { name = data.name; deckLevel = data.deckLevel; bedCount = data.bedCount; } // simple accessors and entity methods public String getName() { return name; } public void setName(String str) { name = str; } // more methods follow }
The getData()
and setData()
methods allow several fields to be packaged into a simple object and
passed between the client and bean in one method call. This is much
more efficient than requiring three separate calls to set the name,
deck level, and bed count.
Here are some guidelines for creating bulk accessors:
Data objects and dependent objects serve clearly different purposes, but they may appear at first to be the same. Where dependent objects represent business concepts, data objects do not; they are simply an efficient way of packaging an entity’s fields for access by clients. Data objects may package dependent objects along with more primitive attributes, but they are not dependent objects themselves.
Keep the data objects as simple as possible; ideally, they should be
similar to a simple struct
in C. In other words,
the data object should not have any business logic at all; it should
only have fields. All the business logic should remain in the entity
bean, where it is centralized and can be maintained easily.
In order to keep the semantics of a C struct
, data
objects should not have accessor (get and set) methods for reading
and writing their fields. The CabinData
class does
not have accessor methods; it only has fields and a couple of
constructors. The lack of accessors reinforces the idea that the data
object exists only to bundle fields together, not to
“behave” in a particular manner. As a design concept, we
want the data object to be a simple structure devoid of behavior;
this is a matter of form following function. The exception is the
multiargument constructor, which is left as a convenience for the
developer.
The bulk accessors can pass a subset of the entity’s data. Some
fields may have different security or transaction needs, which
require that they be accessed separately. In the
CabinBean
, only a subset of the fields
(name
, deckLevel
,
bedCount
) is passed in the data object. The
id
field is not included for several reasons: it
does not describe the business concept, it is already found in the
primary key, and the client should not edit it. The
ship
field is not passed because it should be
updated only by certain individuals; the identities authorized to
change this field are different from the identities allowed to change
the other fields. Similarly, access to the ship
may fall under a different transaction isolation level than the other
fields (e.g., Serializable versus Read Committed).
In addition, it is more efficient to design bulk accessors that pass
logically related fields. In entity beans with many fields, it is
possible to group together certain fields that are normally edited at
the same time. An Employee bean, for example, might have several
fields that are demographic in nature (address
,
phone
, email
) and can be
logically separated from fields that are specific to benefits
(compensation
, 401K
,
health
, vacation
). A group of
logically related fields can have its own bulk accessor. You might
even want several bulk accessors in the same bean:
public interface Employee extends javax.ejb.EJBObject { public EmployeeBenefitsData getBenefitsData() throws RemoteException; public void setBenefitsData(EmployeeBenefitsData data) throws RemoteException; public EmployeeDemographicData getDemographicData() throws RemoteException; public void setDemographicData(EmployeeDemographicData data) throws RemoteException; // more simple accessors and other business methods follow }
Simple accessors (get and set methods for single fields) should not be abandoned when using bulk accessors. It is still important to allow editing of single fields. It’s just as wasteful to use a bulk accessor to change one field as it is to change several fields using simple accessors.
Local references in EJB 2.0 container-managed persistence are very efficient, so the performance benefits of bulk accessors are minimal. If you’re using EJB 2.0, use bulk accessors with remote interfaces whenever it makes sense according to the guidelines given here, but use them sparingly with local interfaces.
The earlier section on passing objects by value gave you some good ground rules for when and how to use pass-by-value in EJB. Business concepts that do not meet the dependent object criteria should be modeled as either session or entity beans. It is easy to mistakenly adopt a strategy of passing business objects that would normally qualify as entity beans (e.g., Customer, Ship, and City) by value to the clients. Overzealous use of bulk accessors that pass data objects loaded with business behavior is bad design. The belief is that passing the entity objects to the client avoids unnecessary network traffic by keeping the set and get methods local. The problem with this approach is object equivalence. Entities are supposed to represent the actual data on the database, which means that they are shared and always reflect the current state of the data. Once an object is resident on the client, it is no longer representative of the data. It is easy for a client to end up with many dirty copies of the same entity, resulting in inconsistent processing and representation of data.
While it is true that the set and get methods of entity objects can introduce a lot of network traffic, implementing pass-by-value objects instead of using entity beans is not the answer. The network problem can be avoided if you stick to the design strategy elaborated throughout this book: remote clients interact primarily with session beans, not entity beans. You can also reduce network traffic significantly by using bulk accessors, provided that these accessors transfer only structures with no business logic. Finally, try to keep the entity beans on the server encapsulated in workflow defined by session beans. This eliminates the network traffic associated with entities, while ensuring that they always represent the correct data.