Saturday, September 04, 2004

How to make a domain object

There are several basic rules for designing a domain object. These rules keep safety and correctness in mind:

Always implement equals and hashCode
I have seen many memory leaks from inadvertent caches of domain objects which used object identity. When implementing equals and hashCode, only compare primary keys since that is the sense of equality the persistence layer uses.
Mark immutable fields final and provide no setters for them
Always mark primary keys final and only set them in a constructor. This means there is no default constructor.
Include all required fields in constructors
Never leave domain objects in an inconsistent state. The constructors should reflect this fact. Again that means no default constuctor.
Test the inputs of non-null fields
Simply provide if (null == someField) throw new NullPointerException(); in the constructor or setter which takes someField. If the field is not required, make the same check in the getter as it may have not been initialized yet.
Implement Comparable if there is a default sort order
Whenever domain objects have any kind of natural sort order, always implmement Comparable. Do not force other code to do the work on behalf of the domain object. And remember to compare as many fields as needed by the sort order. For example, a CustomerName comparator needs to sort on lastName, firstName, middleName and nameSuffix.
Implement toString for debugging
Do not use toString for business code or display. For those, use business-specific methods such as displayAs. Commons-lang provides an excellent ToStringBuilder for debugging.

Unfortunately, not all of them can be used with every project. For example, if you persist domain objects directly rather than using an intermediate persistence layer to represent database tables (e.g., Hibernate or iBatis), the framework requries that every instance field have a bean-like getter/setter. (Actually, recent Hibernate supports direct field access, but this is still uncommon with many projects.)

4 comments:

Anonymous said...

I think that implementing #equals and #hashCode to depend on the primary key(s) is a very bad idea. Imagine the scenario where you have your domain object, modify it and then you compare it against a freshly loaded instance from the DB. Will they be #equal() -- YES. Should they be considered to be equal? Definitely NO. Been there, done that, got the T-Shirt.

Re: "there is no default constructor" -- have you ever actually sent your objects over a physical wire? I.e. by making them serializable? If you ever plan to do this you WILL need a default constructor.

Just my $.02.
Christian

Anonymous said...

The idea of not leaving your objects in an inconsistent state sounds sweet, but really is idealistic nonsense. This makes it ugly for a persistence layer to load the domain object and basically says "I will never want to consider lazy loading". Also, you end up with uberconstructors. Some day you'll change a field to no longer required and then what? Remove it from the constructor? Great, you have to update all your code... not just what accesses the domain objects, but your test cases, javadocs, etc.

On top of that, forget about ever adding factories, pooling, serialization, or distributed management.

Cris
http://crisdaniluk.com/

Anonymous said...
This comment has been removed by a blog administrator.
Brian Oxley said...

I've been down both roads and prefer that persistence objects be fully constructed. I've not had trouble with serialization conserns -- look at non-bean-based implementations such as Walnes' XStream. There is also an interesting question about separating the persistence objects from the domain objects, and making the domain objects use composition only. This changes things a bit.