Application Layers And Data Transfer Objects
Rant Comments OffAsk your buddy J2EE Architect to outline a new system design.
Chances are he is going to start talking layers. Hopefully he is smart/experienced enough not to start with Entity EJBs (however, recently rehyped EJB3 beans may resurrect that dogma). Most likely he is going to describe some kind of Data Access Layer (DAO) magic to talk to the database and end up describing some kind of Service Layer or Business Layer “exposed to the clients”.
If such Architect actually had experience building up a performant system, he will assume all layers living in the same JVM. If he is by the book kind of Architect, he will separate “Service Layer” onto its own set of boxes and claim the victory for Service Oriented Architecture.
And how these newly conjured layers are going to talk to each other?
Well, the DAO layer will get busy running database queries and materializing the Java objects out of thin air based on the result sets for selects and mapping and binding values from the given objects to the database for inserts, updates and deletes.
What is coming out of DAO layer (and what is hopefully captured by set of DAO interfaces) is the number of so called Data Transfer Objects (DTOs) which look sort of not unlike your database schema: Account, Customer, Order, etc. and collections thereof.
Service slash Business Layer will take those DTOs and do something smart with them to implement our “business logic” and then make more instances and collections of DTO-style objects and call the DAO layer again to persist the results.
The most prominent caller of the Service Layer is the always mysterious UI Layer. Most likely, it is just JSPs or some kind of templating thing driven by the MVC framework-of-the-day living in the same VM. But, of course, if your buddy Architect is not ready yet to give up the dream of distributed nirvana, UI layer may live separately on the set of “webhead” boxes. In that case, the hefty doze of remoting (RMI, Spring HTTP kind, ..) is thrown in the picture.
In order to be “clean”, UI layer should be able to manipulate objects coming out of the Service Layer. Being a “customer” of the Service Layer, the UI only has access to the set of interfaces exposed by the Service Layer and the objects manipulated by those interfaces. These objects form yet another set of DTOs. For obvious reasons, these DTOs are very different from DTOs coming out of DAO layer and have to be built up and maintained separately. In addition, if application is multi-tier distributed (Service Layer and UI Layer talk to each other over the network), sooner or later your friendly Architect will attempt to battle inevitable performance issues by making “coarsely grained” requests. What it means, the Service Layer will attempt to minimize number of remote calls by piling up more and more stuff on every request, whether it is related to the original purpose of that requests or not. Regardless of perceived performance gain, it will lead to bigger, badder, harder to maintain DTOs in the Service Layer.
As different, layer-centric DTOs blossom in your application, the code handling them thrives as well. Entire “utility” libraries spring up to convert one type of DTOs to another. What makes this even worse is the fact that those converters often have to make semi-business decisions, for example, how to prune the results from over-eager object graph coming through. Overall the DTO transformation efforts start consuming significant portion of your application time and money.
Hibernate and other object relational mapping (ORM) tools bring interesting twist into this equation. Hibernate goes the distance to make your Java objects easily persistent and hide the ugliness of the database programming. Using Hibernate, programmer suddenly gets to simply save or update his objects as naturally as calling methods or setting properties.
So where does Hibernate fit in your friend Architect’s designs? In the DAO layer of course. In a typical J2EE project, ORM is no more than tools of getting stuff from the database and putting it back in.
Significant effort needs to be put in to ensure that Hibernate mappings, objects and queries truly represent the domain model captured in the database. And all this almost immediately goes down the drain as the DAO layer takes the Hibernate objects coming out of the queries and “converts” them to DTOs that can be understood by the Service Layer and up.
Ask your Architect why his Hibernate-powered DAO layer needs DTOs at all instead of simply passing the “hibernatable” objects out. Most likely you are going to get a lot of hand-waving and glossy-eyed looks we typically get while discussing J2EE “patterns”. If you do get a reasonable response, it is probably going to be centered around the idea that we do not want our DAOs to become “dependent” on Hibernate and we want to be able to swap different DAO implementations in and out as needed. The magical word “reuse” will be brought to the altar.
This attitude is unfortunately shared by many architect types but is wrong nevertheless. Hibernate objects are simple Java beans and as such carry no Hibernate dependencies. The real reason is that very few take their time to learn how to properly attach and detach objects to and from the Hibernate session and how to handle lazy initialization of “getChildren” collections. Did I get null while calling getChildren() because there were none or because we already closed the Hibernate session and this particular relation was set to be lazy-inited?
The real problem with DTOs is rooted in the reality that few of the currently developed J2EE applications actually care about the object model. In fact, the whole J2EE mentality has been anit-OOP from the very beginning. (Don’t get me started on EJB designs).
The solution is the embrace the “objectness” of your domain model. In case of Hibernate, do invest time developing mapping and queries. But instead of making Hibernate a poor bastard child of the DAO layer, make Hibernate the cornerstone of your design, use it to fully capture the intricacies of your domain model. There should be no other place you need to go to learn about your Customer model but “Customer” object mapped by Hibernate. Hibernate classes should become the primary, authoritative source of domain knowledge. And having done that, would you still throw all the knowledge away for sake of translating to Hibernate-less DTOs? Of course not.
Hibernate classes are first class object citizens. Use them to pass the data out of DAO layer or of any layer. Do away with the need to perform endless transformations from one set of DTOs to another to another as you logic traverses the application layers.
There is nothing to stop you to use the core Hibernate model classes even as part of the UI layer. Instead of creating yet another object model acting as DTOs for UI (think Struts form objects), just use the core objects directly or with slight massaging applied. So instead of using constructs like
${customerForm.firstName}
, inject the domain objects into the UI forms:
${form.customer.firstName}
.
And how to deal with lazy initialization and the danger of pulling too much from the object graph? – one might ask.
If your application is not deployed on multiple network tiers and, instead, the entire stack runs in the same VM (as it should be for maximum scalability), the solution is simple – just let Hibernate do its thing and proxy the calls to the collections as needed. So if inside of your UI, your form feels like looping through some records using forEach, just let it.
If this particular relation is lazy-initialized in Hibernate, the corresponding database call will only be made when Struts (or whatever) gets to execute this loop in JSP. Of course, this will only work if you employ Hibernate “Open Session in View” pattern (or its Spring equivalent).
This is where patterns police sees the red: this call in fact bypasses their precious DAO layer and gets executed much later in the game.
But after all is said and done, it is your call on the tradeoffs between perceived pureness of the design and enormous simplification obtained by getting rid of multilayered DTO structures and support.