-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 15 posts ] 
Author Message
 Post subject: Smart domain objects? or simple DTO objects?
PostPosted: Mon Feb 09, 2004 2:15 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
We're grappling with a tension in our architecture, and we're looking for all feedback on how others deal with this.

We have our domain model consisting of clients, campaigns, lists, jobs, tasks, etc. We're using Hibernate (of course).

We want to do two things:

- We want to add interesting business behavior to these objects, to enrich our domain model and make it easier to implement business logic. (Basically, to convert these objects from plain data beans to intelligent business objects.)

- We want to pass these objects to the web tier, so the web tier can update them appropriately and then pass them back to the service layer. (Basically, to use these objects as DTOs (Data Transfer Objects).)

These two goals seem to be in conflict. It's nice that with Hibernate (unlike with entity beans) we *can* pass our domain model objects right out to the web tier, for presentation or basic modification. But the more business methods we add to our domain objects, the more methods they will have that *can't* be safely called from the web tier.

In other words, DTO-type domain objects want to have simple APIs that just get and set data (that can be safely called from the web tier, followed by passing them back to the service layer for saveOrUpdate); rich business-logic domain objects want to have complex APIs that *cannot* be called from the web tier (only inside the container where there is a database connection and transaction context).

So what do other people do? Use XDoclet to generate DTOs? Use some basic conventions (naming conventions?) for indicating which methods are and aren't safe to call from the web tier? Use package scope to keep the business logic methods only usable from other business objects?

All guidance appreciated :-)
Cheers!
Rob


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 2:54 pm 
Hibernate Team
Hibernate Team

Joined: Tue Sep 09, 2003 2:10 pm
Posts: 3246
Location: Passau, Germany
Why don't you use something like the open session in view pattern, this will allow you to use all kinds of lazy loading and such in the web tier.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 2:59 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
I don't *want* the web tier to have access to the database layer. (Otherwise what's to stop the web tier from deleting objects, calling business methods, breaching security checks at the service layer, or all kinds of other bad things?)

When I said I want the web tier to have access only to simple DTO-like methods with no persistence behavior, I meant it ;-)

Cheers!
Rob


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 3:02 pm 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
And again: An event listener that opens and closes the session on particular events is _NOT_ a strong coupling of the presentation to the persistence layer. It is part of the SYSTEM layer that manages resource for all other layers. There is no dependency issue here.

_________________
JAVA PERSISTENCE WITH HIBERNATE
http://jpwh.org
Get the book, training, and consulting for your Hibernate team.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 3:05 pm 
Hibernate Team
Hibernate Team

Joined: Tue Sep 09, 2003 2:10 pm
Posts: 3246
Location: Passau, Germany
By having your buisness methods access DAOs, you still decouple the view from the persistence layer, and can later phase it out or change it. IMHO there is not much sense in this ugly DTO pattern, which was really just a fix for the shortcomings of Entity Beans. If you need to "stop" your developers from doing some things like randomly deleting objects, you have a problem anyways.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 4:05 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
OK, wasn't expecting this to go in this particular direction, but oh well:

We have a service layer (stateless session beans). The reason we have the service layer is so that we don't get any logic in our presentation layer. This is to ensure that if we want to expose our system through web services, or if we want to swap out our entire web tier (change from Struts to WebWork or whatever else), no business logic will change at all.

For using web services or whatever else, there is no way that the "session in view" pattern can possibly work, since the client will not have any access whatsoever to the data source. So forget about having any web-layer persistence session interceptors or view sessions. We are trying to design the system so that the web tier / presentation layer is totally decoupled from all persistence services, in order to support clients that *won't* be able to get at our persistence data sources. (And to strengthen and centralize our security boundary, as well.)

So yes, this leaves us in a situation where our domain objects -- which we want to have rich behavior -- may have a bunch of methods that could fail if called in the (persistence-disconnected) presentation layer. Some other threads (on theserverside) suggest using a style guideline: presentation layer should call only simple getter/setter methods on domain objects passed to it (essentially using only the DTO-like methods of the domain objects). This has some promise, I think.

Any more feedback on this? -- it may not be Hibernate-specific enough anymore, but it's definitely one of the Eternal Topics in J2EE land....

Cheers!
Rob


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 5:56 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
use the same design as yours Rob, but my domain objects stay clean of any business method.
Every business methods are done in a process layer (same or finer than service layer). It helps me to use my domain model as DTO and stay focused on SOA. Of course I've a bit of OO.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 6:30 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
Alright, let's bring this back specifically to Hibernate, and make a specific Hibernate extension proposal. If no one else thinks this is a good idea, but we wind up wanting it badly enough, perhaps we'll implement it and submit it to JIRA! (What licensing issues would that bring up, though? Sigh....)

First off: we are using hbm2java to generate our base persistent Java classes. These hbm2java-generated Java classes really *are* DTOs -- they contain no business logic, just the persistent data of our domain model. (We assume that it's OK to expose our basic domain model entities and relationships as DTOs, since we don't see the need for another whole layer of code.)

We want to extend these Hibernate-generated DTO-like classes with business logic. We'll be using <meta attribute="generated-class"> for this (as suggested in other threads I started ;-).

Let's look at a specific scenario: we have a Campaign object which has a parent Client object. We want to put interesting business methods (for example, "delete" which will remove the parent's link) on the Campaign object. We also want to pass Campaign instances to the presentation tier (which has NO ability to reach or use the persistence layer). But we don't want the business methods to be callable by the presentation tier.

In Hibernate 2.1.1, we can do this:

Code:
<hibernate-mapping>

    <class name="com.nimblefish.core.data.Campaign">
        <meta attribute="generated-class">com.nimblefish.core.data.CampaignDTO</meta>

        <id name="id" type="long" unsaved-value="null" >
            <generator class="native"/>
        </id>

        <property name="name" type="string"/>

        <many-to-one name="client" column="client_id" not-null="true"
            class="com.nimblefish.core.data.Client"/>

        <!-- etc., etc. -->

    </class>

</hibernate-mapping>


This generates the class:

Code:
/** @author Hibernate CodeGenerator */
public class CampaignDTO implements Serializable {

    /** identifier field */
    private Long id;

    /** nullable persistent field */
    private String name;

    /** persistent field */
    private com.nimblefish.core.data.Client client;

    /** full constructor */
    public CampaignDTO(String name, com.nimblefish.core.data.Client client) {
        this.name = name;
        this.client = client;
    }

    /** default constructor */
    public CampaignDTO() {
    }

    /** minimal constructor */
    public CampaignDTO(com.nimblefish.core.data.Client client) {
        this.client = client;
    }

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public com.nimblefish.core.data.Client getClient() {
        return this.client;
    }

    public void setClient(com.nimblefish.core.data.Client client) {
        this.client = client;
    }
}


And we then subclass it:

Code:
public class Campaign extends CampaignDTO {
    public void delete () {
        // disconnect the parent-child association
        this.getClient().getCampaigns().remove(this);
        Persistence.session().delete(this);
    }
    public float calculateCampaignValue () {
         // do some major business logic....
    }
}


Client is implemented exactly the same way - the mapping specifies the class name "Client", the generated-class is "ClientDTO", and we subclass it to add the business logic to the actual "Client" class.

Now, what we really want to be able to pass back to the presentation tier is the CampaignDTO, not the Campaign.

The problem is that the CampaignDTO has a binding to the Client class, not the ClientDTO class. In other words, CampaignDTO has a getClient method, which exposes the Client business object, rather than a getClientDTO method, which would just expose the transfer object.

In some sense, what we want is to have Hibernate generate is something more like this:

Code:
/** @author Hibernate CodeGenerator */
public class CampaignDTO implements Serializable {

    /** identifier field */
    private Long id;

    /** nullable persistent field */
    private String name;

    /** persistent field */
    private com.nimblefish.core.data.ClientDTO client;

    /** full constructor */
    public CampaignDTO(String name, com.nimblefish.core.data.ClientDTO client) {
        this.name = name;
        this.client = client;
    }

    /** default constructor */
    public CampaignDTO() {
    }

    /** minimal constructor */
    public CampaignDTO(com.nimblefish.core.data.ClientDTO client) {
        this.client = client;
    }

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public com.nimblefish.core.data.ClientDTO getClientDTO() {
        return this.client;
    }

    public void setClientDTO(com.nimblefish.core.data.ClientDTO client) {
        this.client = client;
    }
}


Then we could extend our child object as follows:

Code:
public class Campaign extends CampaignDTO {
    public Client getClient () { return (Client)getClientDTO(); }
    public void setClient (Client client) { setClientDTO(client); }
    public void delete () {
        // disconnect the parent-child association
        this.getClient().getCampaigns().remove(this);
        Persistence.session().delete(this);
    }
    public float calculateCampaignValue () {
         // do some major business logic....
    }
}


This basically leaves the Campaign interface completely unchanged. However, we can then write a generic domain-objects-to-DTO-objects mapper that walks over some domain object, constructing a set of DTO objects (using totally generic reflection), handling however much (or little) transitive loading as is appropriate in the given use case. (For example, we could take a Campaign object which has a Client object, and create a CampaignDTO object which has a ClientDTO object. Exactly the same object graph, only with just the DTO superclass instances.)

Then all we ever need to expose to the client is the DTO object classes, which have no reference whatsoever to the business object classes.

I guess what I'm really asking for is some new meta tag on properties, something like this:

Code:
<hibernate-mapping>

    <class name="com.nimblefish.core.data.Campaign">
        <meta attribute="generated-class">com.nimblefish.core.data.CampaignDTO</meta>

        <id name="id" type="long" unsaved-value="null" >
            <generator class="native"/>
        </id>

        <property name="name" type="string"/>

        <many-to-one name="client" column="client_id" not-null="true"
            class="com.nimblefish.core.data.Client">
            <meta attribute="declared-type" class="com.nimblefish.core.data.ClientDTO"/>
        </many-to-one>

        <!-- etc., etc. -->

    </class>

</hibernate-mapping>


This would then emit ClientDTO (which is required to be a superclass of Client!) as the declared type of the "client" property of CampaignDTO. CampaignDTO would have a "client" instance variable with type "ClientDTO", but would have "getClientDTO" and "setClientDTO" (*not* "getClient" and "setClient"!) getters and setters. The Client class could be required to implement the actual "getClient" and "setClient" getter/setters.

However, Hibernate would still know that Client is the actual persistent object class to load and to persist -- in fact, it's possible that little (or none?!) of the Hibernate runtime would need to change at all!

The overall goal here, again, is to allow the behavior -- and the domain logic classes themselves -- to be kept out of the data transfer objects. So clients of the service layer would only ever see, or need to load, the DTO classes. (This keeps the clients from being exposed to changes in the business logic classes.)

What do you think? Not asking for much, am I? ;-D
Cheers!
Rob


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 7:22 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 7:19 pm
Posts: 2364
Location: Brisbane, Australia
Send the request to JIRA.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 7:41 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
Done. Thanks! I will work on a patch, but can't do it on work time, so no guarantees on timing....

Cheers!
Rob


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 09, 2004 7:50 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
(p.s. http://opensource.atlassian.com/projects/hibernate/secure/ViewIssue.jspa?key=HB-700 if anyone wants to vote for it ;-)


Top
 Profile  
 
 Post subject:
PostPosted: Tue Feb 10, 2004 2:11 am 
CGLIB Developer
CGLIB Developer

Joined: Thu Aug 28, 2003 1:44 pm
Posts: 1217
Location: Vilnius, Lithuania
RobJellinghaus wrote:
Any more feedback on this? -- it may not be Hibernate-specific enough anymore, but it's definitely one of the Eternal Topics in J2EE land....

Cheers!
Rob


I see nothing wrong in your architecture. If this way is more natural "model" use it. You do not need to solve problem if it doe's not exists. Application use cases and logic must dictate application architecture, not religinos or buzz words like AOP,OPP,SOA, ... App must have some motivation to use some architecture.
I think your motivation is good to use your way.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 12, 2004 3:06 am 
Newbie

Joined: Fri Oct 03, 2003 1:10 am
Posts: 10
Rob,

Your approach (generating your domain objects from hbm2java, extending them with subclasses that implement business logic) is the same as ours.

You asked what others do to provide DTO's. What we do is use a generic DTO class that stores domain object properties in a Map indexed by property name. Given a domain object instance, the Hibernate metadata provides everything you need to instantiate such a DTO containing the properties of the domain object with generic code. Our presentation tier code deals exclusively in these generic DTO's - it is not directly exposed to the domain model at all.

Once you've bought into this idea, you can go on to write generic code for basic CRUD operations including list views of domain objects, configurable forms for editting simple domain objects, etc.

Since we end up referring to domain classes and their properties by name in a lot of the presentation code, we also have an XSL that takes the Hibernate mapping file and generates classes to define constants for all the property names - don't want people hardcoding strings all over the place or you can't refactor safely.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 12, 2004 2:58 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
Yes, I see how you could provide a dynamically-typed generic DTO for the UI side. We are still pretty attached to the idea of preserving strong typing in the objects sent out in our service layer's API, so I think the "make the DTO generated superclasses be a closed set" pattern is still worth following. (Layering the "generic list view creation" functionality, etc., on top of that is still eminently doable.)

The HB-700 JIRA entry I created for this has evolved the whole proposal considerably -- thanks Max for the helpful clues! I actually implemented an hbm2java patch last night and will be submitting it at some point today.

Cheers!
Rob


Top
 Profile  
 
 Post subject: Re: Smart domain objects? or simple DTO objects?
PostPosted: Sun Aug 14, 2011 4:40 pm 
Newbie

Joined: Wed Jun 08, 2011 6:28 pm
Posts: 5
Classic problem, and it's been unbelievably arcane and awkward to solve.

Now there's this:

Code:
Constraint: balance < creditLimit

balance = sum(orders.total where paid = false)

order.total = sum(lineitems.amount)

lineitem.amount = quantity * partPrice

lineitem.partPrice = copy(part.price)


This is executable - it plugs into Hibernate / JPA events (so no changes to your / your frameworks Hibernate code), and is enforced for all transactions that update the relevant Domain Objects (delete an order, associate line item to different part, change a quantity, etc etc).


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 15 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.