9.2. Passing Objects by ValuePassing 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. 9.2.1. Dependent ObjectsDependent 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.
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 void removeAddress(Address adrs) throws RemoteException; public void addAddress(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. 9.2.2. Validation Rules in Dependent ObjectsDependent 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. 9.2.3. Bulk AccessorsMost 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. 9.2.3.1. Rules-of-thumb for bulk accessors
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 }
9.2.4. Entity ObjectsThe 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. Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|