Passing objects by valueis 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.
Dependent objects are objects that only have meaning within the
context of another business object. They typically represent fairly
fine-grained business concepts, like an address, phone number, or
order item. For example, an address has little meaning when it is not
associated with a business object like 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 Address
. In turn, the
Address
object is usually an attribute or property
of another business object; in EJB, we would typically see an
Address
or some other dependent object as a
property of an entity bean.
Here’s a typical implementation of an
Address
:
public class Address 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 don’t change an
Address
’s fields. The reason is quite
simple: the Address
object is a copy, not a remote
reference. Changes to Address
objects are not
reflected in the entity from which it originated. If the client were
to change the Address
object, those changes would
not be reflected in the database. Making the
Address
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 is reflected on the server.
Some EJB 1.0 servers that use early versions of CORBA IIOP do not support passing objects by value. With these vendors, dependent objects must declare their fields as public. Of course, since the fields are public, the client can modify them directly. In these cases you need to trust your client programmers to exercise some discipline in the way they use the Address
object.
To change an address, the client is required to remove the
Address
object and add a new one with the changes.
This enforces the idea that the dependent object is not a remote
object and that 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 Address []getAddresses
() throws RemoteException; public voidremoveAddress
(Address adrs) throws RemoteException; public voidaddAddress
(Address 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
Address
objects. To remove an address, the target
Address
is passed back to the bean in the
removeAddress()
method. The bean class then
removes the matching Address
object from its
persistent fields. To add an address, an Address
object is passed to the bean by value.
Dependent objects may be persistent fields, or they may be properties
that are created as needed. The following code demonstrates both
strategies using the Address
object. In the first
listing, the Address
object is a persistent field,
while in the second the Address
object is a
property that doesn’t correspond to any single field; we create
the Address
object as needed but don’t save
it as part of the bean. Instead, the Address
object corresponds to four persistent fields:
street
, city
,
state
, and zip
.
// Address as a persistent field public class Person extends javax.ejb.EntityBean { public Address address; public Address getAddress(){ return address; } public void setAddress(Address 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 Address getAddress(){ return new Address(street, city, state, zip); } public void setAddress(Address 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 persistent 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 beans—entity or session. 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. 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. By placing the validation logic in the constructor of a dependent object, the object automatically validates data when it is created. When data is entered at the UI (GUI, Servlet, JSP, or whatever) it can be validated by the UI 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 ZipCode 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 requiring any interaction with the middle tier
of the application. In addition, any business object that uses
ZipCode
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 only useful for validation if the Enterprise JavaBeans
implementation supports pass-by-value. Some of the EJB 1.0
CORBA-based systems only support a crude form of pass-by-value that
uses CORBA structures, which prevents you from using dependent
objects that incorporate validation rules.
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,
the accessors could incorporate the validation code. While this 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 persistent fields that are manipulated through accessor methods. Unfortunately, the one-to-one nature of the accessor idiom can result in many invocations when editing an entity, which translates into a lot of network traffic even for simple edits. 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 editing entities is to use bulk accessors. This strategy packages access to several persistent fields into one bulk accessor. 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.
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 easily maintained. In addition,
some EJB systems based on CORBA 2.0 may not be capable of passing
complex objects by value.
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
doesn’t 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;
it’s a matter of form following function. The exception is the
multi-argument 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
) are passed in the data object. The
id
field is not included for several reasons: it
doesn’t describe the business concept, it’s already found
in the primary key, and the client should not edit it. The
ship
field is not passed because it should only be
updated 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’s more efficient to design bulk accessors that pass logically related fields. In entity beans with many fields, it is possible to group certain fields that are normally edited together. An employee bean, for example, might have several fields that are demographic in nature (address, phone, email) that can be logically separated from fields that are specific to benefits (compensation, 401K, health, vacation). Logically related fields can have their 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.
The pass-by-value section earlier 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’s easy to mistakenly adopt a strategy of passing business objects that would normally qualify as entity beans (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’s 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 only transfer 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.