Book Home Enterprise JavaBeans Search this book

9.2. Passing Objects by Value

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.

9.2.1. Dependent Objects

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.

NOTE

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 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 Objects

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.

9.2.3. Bulk Accessors

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.

9.2.3.1. Rules-of-thumb for bulk accessors

Data objects are not dependent objects

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.

Data objects are simple structures

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.

Bulk accessors bundle related fields

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 

}
Retain simple accessors

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.

9.2.4. Entity Objects

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.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.