Domain Objects
Domain objects in Java should always reflect the domain model as outlined in the database schema. The crux of a good framework is that the data model is represented in an appropriate fashion in the database. If this is not the case there is no point in writing software to work with a broken or poorly designed database schema.
Domain objects should all contain a few basic elements.
- Common Domain Object
- Primary Identifiers
- Static Domain Model Data
- Implement Serializable
- Working equals method
- Working hashCode method
Common Domain Object
A common Domain object for all classes helps keep certain functionality among your domain model consistent. It also allows for the use of reflection to dynamically compute equals, hashCode and toString methods with the help of the Commons Lang API.
public abstract class Domain implements Serializable {
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
Primary Identifiers
I usually use a class named Identifier that uses a Long as the basis for Id’s in objects. This object is serializable, as an extension of Domain, since identifiers will probably consistently traverse the wire.
public class Identifier extends Domain {
//Override and implement parent constructors
private Long id;
}
Each domain object should have a corresponding table in the database schema that has a primary key. This key is integral in the identification of individual domain objects. Before creating a class to represent a Car, you should create a class to represent the identifier.
public class CarId extends Identifier { ... }
After creating the identifier the class representing a Car will have a CardId as a member variable.
public class Car {
private CarId carId;
//Implement getters and setters
}
Implement Serializable
Implementing Serializable on domain objects is a must. You always want these items to be able to traverse a networked connection as typically you won’t know what future uses your framework may be used for. This is a very simple addition to our Car class through the extension of Domain.
public class Car extends Domain {
private CarId carId;
//Implement getters and setters
}
Equals Method
While the commons lang library does a great job for easily implementing equals, you may still have a need to write your equals method. Every domain object requires that a proper equals method be created. I have found that a long string of booleans anded together into a single variable works well due to Java’s lazy evaluation of boolean expressions. In other words, if the 2nd of 100 attributes doesn’t match, you get out immediately and don’t worry comparing the rest of the object.
public class Car extends Domain {
private CarId carId;
private Integer mileage;
public boolean equals(Object o) {
boolean equal = false; //Default to false
if (o instanceof Car) { //If the object we're comparing to isn't a Car, get out
Car that = (Car) o; //Cast that object we're comparing against to a Car
equal = //Chain a set of boolean expressions together with logical AND's
this.getCarId().equals(that.getCarId())
&&
this.getMileage().equals(that.getMileage());
}
return equal;
}
}
Implementing hashCode
Efficient caches require that a proper implementation of hashCode. Typically you want to spread your hashCode’s key space out so that each object will have a unique hash value. While this is not a requirement, hashCode could simply return a single number for every instance of a domain object. One of the easiest solutions is to have a domain object return it’s primary identifier as the hashCode.
From the Java API Documentation for HashMap.
This implementation provides constant-time performance for the basic operations (get and put), assuming the hash function disperses the elements properly among the buckets.
If you don’t want to take the lazy route and return the primary identifier value, you can compute a hashcode very quickly and easily using XOR and shifts. The shifting allows you to push the primary identifier into the higher order bits to allow for spreading the keys out among the hashCode space more effectively. Here is a sample implementation if you choose to not use the default provided by Commons Lang
public int hashCode() {
int code = 0;
code ^= (carId.hashCode() << 16);
code ^= mileage.hashCode();
return code;
}