Yaozong has posted 2 posts at DZone. View Full User Profile

Domain Objects and Their Variants

12.21.2010
| 5954 views |
  • submit to reddit

I read "Domain Object Dependency Injection with Spring" the other day. Actually, what interested me were those comments about the concepts and patterns around "Domain Object". I had been always confused about those buzzwords related to Domain Object: Domain Object, Business Object, DTO, POJO, Anemic Domain Object, Active Record Pattern.

After some research, I found several points critical to help understand those concepts (at least for myself):

  1. Among those concepts, POJO is not that relevant.
    It's a general principal applied to all enterprise beans (service beans, dao beans, etc.) not to be framework-specifc, though framework-specific annotations are commonly used in 'POJO' these days.
  2. Domain Object == Business Object.
    They are entity representitives in business layer, which shoud be understood by non-programming people, such as business analysts.
  3. Business Object should have both data and behaviour.
    Anemic Domain Object are those business objects only have data without behaviour.
  4. Data Transfer Object (DTO) is a concept within data access layer.
    DTOs are often used in conjunction with DAOs to represent data retrieved from a DB.
  5. DTOs and Domain Objects can be designed to use the same class, even represented by the same object for the same entity. Personally, I think Domain Objects refer to the combination of DTO and Business Objects for plenty of cases.
  6. Active Record Pattern:
    Domain objects have rich behaviour, for example, inject DAO into domain objects to make it has CRUD ability.

Understanding the above concepts is just half way, I would like to going further for things behind these domain object variants:

On the one hand, people like Martin Fowler thinks domain object should have data and behaviour, which is consistent with OOD. Anemic Domain Object is anti-pattern, which in effect causes 'procedure script' style thick service layer. On the other hand, anemic domain objects are still popular. For myself, the following two facts explain the phenomenon:

  1. Active Record Pattern is really painful.
    I don't like the idea of putting DAO into Domain Object at all. And the clear multi-layer design, makes coding more intuitive and very easy to maintain.
  2. Encapsulating behaviour into business object doesn't mean removing service layer at all.
    Just don't put all behaviour into service layer, this layer should be as thin as possible.

What I learned:

  1. I will put behaviour only relevant to the individual entity into domain object, for example: validation, calculation, maybe CRUD (I still prefer having separate DAO to fufill the tasks, don't like any huge object)
  2. Service layer should serve as a gateway to all above layer, which means any business atomic operation should find its place in service layer. Then, transaction can be managed in one layer.
  3. Behaviour involves different entities regardless same type of entity or different types may go into service layer.

 

This is my personal opinion based on what I understand so far. Any comments or critisim are welcome.

PS. This is my first post:) Wish it good luck, and Merry Christmas to everyone.

More to say:

I've read the "Domain Driven Design" refcardz after writing this article, found out some similar points were already mentioned there, though DDD covers broad aspects. I think my article exposes some facts or problems of this topic, which would help people obtain deep insides via further reading. Actually, I am planning to read some DDD book as the second session after current looking up.

Published at DZone with permission of its author, Yaozong Zhu.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Wojciech Gdela replied on Tue, 2010/12/21 - 6:57am

"Behaviour involves different entities regardless same type of entity or different types may go into service layer". I wouldn't say that. See for example:

// service layer: 
void marry(int manId, int womanId) {
  transaction.begin();
  Man man = dao.loadMan(manId);
  Woman woman = dao.loadWoman(womanId);
  man.marry(woman);
  transaction.commit();
}

 

// domain class Man:
void marry(Woman woman) {
  this.wife = woman;
  woman.husband = this;
  woman.surname = this.surname;
}


Or would you like to marry a woman without seeing her?

Yaozong Zhu replied on Tue, 2010/12/21 - 7:59am in response to: Wojciech Gdela

Hi Wojciech,

I don't get you. What you showed actually justified my point that adding behaviour involving multipe domain objects (Man instance, and Woman instance) into service layer.

But I think the example forces the existence of service layer because you use "int manId, int womanId", the "logic to load Man and Women by ids" simply can't go into domain object.

How about the layer above passes Man and Women objects with id populated (I think this is OOP):

// service layer: 
void marry(Man man, Woman woman) { //id populated
transaction.begin();
man.marry(woman);
transaction.commit();
}

// domain class Man:
void marry(Woman woman) {
this.wife = woman;
woman.husband = this;
woman.surname = this.surname;
}

In this case, people would argue if the service layer is necessary. And it's repository layer's responsibility to update those two Domain objects into DB.

 

Chris Knoll replied on Tue, 2010/12/21 - 9:00am

Yao, I think you have some good ideas from your first post, but unfortunately you fell into Woj's trap about behavior in domain objects.  Specifically, why would you bother putting marry() in a domain object when you said that you wouldn't want to do that in your first post.  I happen to agree with you.  Instead, this is what I'd do:

Domain Objects:
Main
Woman

Service Layer:
Minister (that's sorta a joke, instead how about MarriageService)

So, why have marry() a core function of what a Man can do?  Is that really what defines it?  What if later you allow Woman to do it (I really don't want to get this into some political discussion....)  Wouldn't it be better to just externalize those capabilities into a service?

marriageService.marry(Man, Woman);

// Later service can support divorces and annulments without changing your domain objects

marriageService.divorce(Man);
marriageService.divorce(Woman);
marriageService.annul(Man);

// Better yet, maybe the marriageService should return MarriageCertificates representing proof of status:

MarriageCertificate cert = marriageService.marry(Man, Woman);

marriageService.divorce(cert);
marriageService.annul(cert);

 

In any case,  I agree with what your origional post says, but I am not sure your ideas will get much traction in this community as there is some pretty deep-seeded GOF-worship, 'Java Rules' mongers and other 'pattern radicals' that won't even consider the things you put forth, but I think keep following that path and you'll have good system design that's easy to maintain.

 

Caspar MacRae replied on Tue, 2010/12/21 - 9:23am

Hi Yaozong, 

I'd say you've correctly evaluated the state of play wrt domain logic and it's place - so good article, thanks.

 

ARP looks nice at first glance but has some huge ramifications;  if persistence is to be exposed at every level then so must validation and verification, and for that matter authorization (you must now know and trust your client application).

Not only this but every subsequent subsystem (tier) must take this into account - if the domain object is send over the wire then the DAO must be transient and re-instated on the client side (so either your persistence host is open all connections or you proxy the DAO? wtf)

 

Some domain objects are just naturally amenic - in another language (other than Java) you probably wouldn't code them as classes.  The " 'procedure script' style thick service layer" is IMHO the fault of awful JEE/EJB specs which in turn produced antipatterns like DTO.

I agree whole-heartedly with your view of where business/domain logic should live - as close the primary entity it involves. 

But one must be sensible about the involved parties - in the above marriage example the logic is limited enough to warrant keeping it in place, but as soon as you add the state/church authority for a marriage licence, which is required to check against some datasource etc then the entire logic should be encapsulated as a service (and if it had been coded as this from the start then clients would be unaffected by these refactorings).

Services provide clear boundaries, enforcing a tiered architecture.  Everyone hates services that merely delegate straight to a DAO, but you can have a generalised CRUD service to aleviate this.  Persistence isn't a cross cutting concern, it's a service to be provided.

If writing a service is a major undertaking (webservices etc) then the SOA framework is at fault - Peter Kriens (OSGi) talks of μServices, which are truely a joy to write and use:  http://www.slideshare.net/pkriens/services-5713195

Yaozong Zhu replied on Tue, 2010/12/21 - 10:08am in response to: Chris Knoll

Chris, my opinion hasn't changed even in my previous reply. Within the reply to Wojciech's example:

Firstly, I don't understand why he gave an example actually justifying my opinion "Behaviour involves different entities regardless same type of entity or different types may go into service layer", but he expressed disagreement on it.

Secondly, I think Wojciech's example was not proper to justify why he agrees|disagrees with the idea of "Behaviour involves different entities regardless same type of entity or different types may go into service layer". Because, he enforces "the logic of loading domain object by id", effectively, enforces the existence of service layer. But my concern is to make choice between active domain objects and anemic domain objects. With his example, there's no choice can be made. Obviously injecting the logic load Man or Woman by id into either Man or Woman domain objects are not right.

So I changed his example a bit in order for people to make choice between with or without service layer. But my original opinion does not change:)

With your example, my opinion is whether a Minister Service or Domain Bean or no bean needed (my example) is really to do with model design, which is driven by concrete story. It's arbitary to say which way is more proper than others without context. To simplify problem and justify my opinion, I think my example is enough.

If I use your example to demonstrate between rich domain objects and anemic ones, obviously yours is on anemic side, on the other side I would rename MarriageService to CivilServant and make it as a domain object (note: not my opinion). Then people can simply make their choice.

Wojciech Gdela replied on Wed, 2010/12/22 - 10:25am

Here's how I see "Behaviour involves different entities regardless same type of entity or different types may go into service layer":

 

// presentation layer

class MarriageAction {
void execute(HttpServletRequest request, HttpServletResponse response) {
service.marry(request.getAttribute("manId"), request.getAttribute("womanId");
}
}

// service layer

class MarriageService {
void marry(int manId, int womanId) {
transaction.begin();
Man man = dao.loadMan(manId);
Woman woman = dao.loadWoman(womanId);
man.wife = woman;
woman.husband = man;
woman.surname = man.surname;
transaction.commit();
}
}

// domain layer

class Man {
// only behaviour relevant to the individual entity
}

 

And I prefer to put all business behaviour into domain layer, like this:

 
// presentation layer

class MarriageAction {
void execute(HttpServletRequest request, HttpServletResponse response) {
service.marry(request.getAttribute("manId"), request.getAttribute("womanId");
}
}

// service layer

class MarriageService {
void marry(int manId, int womanId) {
transaction.begin();
Man man = dao.loadMan(manId);
Woman woman = dao.loadWoman(womanId);
man.marry(woman);
transaction.commit();
}
}

// domain layer

class Man {
void marry(Woman woman) {
this.wife = woman;
woman.husband = this;
woman.surname = this.surname;
}
}

 

  • While implementing domain I don't have to think about persistence, database access or transaction.
  • While implementing service I don't have to think about HTTP.
  • In presentation layer I like to focus on presentation. It's service layer responsibility to find objects in database (using dao), tell them what to do, and wrap all of it in atomic transaction.
  • This example is simplified. If you have to model CivilServant or MarriageCertificate do it, but don't tell them to manage transaction or care about finding people to marry.

 

My point was to show that even behaviour involving different entities should go into domain layer. So I totally agree with Yaozong that service layer should be as thin as possible.

You can do without service layer, but you will have more concerns to cope with in presentation layer (managing persistence and transactions) and it will be cumbersome if you have more than one presentation (i.e. web application and soa service).

Chris Knoll replied on Wed, 2010/12/22 - 2:17pm in response to: Wojciech Gdela

Yeah...I can't really see putting marry() in the Man object. Every time you add a new behavior your entire domain is impacted.  I'm not sure why thie 'behavior in domain object' is so prevalent in the JavaLobby community, but isn't it simple to see that every time you want to introduce a new behavior or action in your system you need to go back and change your domain model?  Instead, just adding services to your system and allowing the service to understand the interaction interactions and relationsihps between domain objects without the domain objects needing to know about it themselves.  To me, this allows the overall system to be flexible in it's design and direction because you can combine domain elemetns together without the domain object locking the system into a particular perspective. You can introduce and retire services without changing any of your domain objects.  

 As a separate example, if you had a library system with the Library and Book, would you put checkin and checkout on the Book?  Or let's say you had a cvs type of filesystem that you wanted to implement ontop of a non-version supported filesystem...would you expect File to be subclassed into a VersionedFile that has Checkin and Checkout on it or would you just ahve a DocumentManager that can manage checkin and checkout procedures for Files?

All this behaviour in the domain object...just makes me shake my head.

 

Wojciech Gdela replied on Thu, 2010/12/23 - 9:25am

I think you get "service" too literally, and you assume that Library is a service (because library lends you a book) and Book is domain (because it can't do anything for you). Please read http://www.informit.com/articles/article.aspx?p=1398617&seqNum=4 to understand what Service Layer pattern is.

So answering your questions I would put checkin and checkout in Library class, but the Library class belongs to domain layer (it know how to lend books, but does not know about database transactions, etc.)

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.