Don’t castrate your architecture
Note: I thought about trying to find a more PC title for this post, but I don’t think that I can really find a good one that express the same emotional content and the punch that this title have.
A few weeks ago I got my first interesting cold call (someone calling me from the number in the blog without some previous acquaintance), cold calling doesn’t happen very often (maybe 5 – 7 times in the 5 years I had the number there), but that was the first time that I actually got to talk to someone about an interesting and relevant problem.
Anyway, they were building a multi tier system using NHibernate, and they were running into problems with lazy loading over WCF. I quickly pointed out that I really don’t like this approach, it has more than technological faults, it has serous architectural issues.
But let us try to get some idea about how they structured they application. It looked something like this:
I think that you can figure out how a normal request would work, but let me spell it out for you.
Please note that this diagram shows the communication between tiers, that is, each of those is a separate machine.
The idea, as far as I could understand, was to use NHibernate in the Data tier, to get entities from the database and then send them to the business tier where it would do business logic processing, after which it may return it back to the Data tier, which would write them to the database.
I was… at a loss for some time, trying to find a way to explain how screwy this architecture was. It quickly became evident that while the guy on the other side of the line wasn’t aware of my reservations, he certainly felt the pain of this type of architecture:
- Slow response times
- Lazy loading over WCF
- Need to handle change tracking on the Business tier
Those are the problem that they already experienced directly. I can add a few more:
- Anemic domain model (by design!)
- Required manual caching
- Required distributed transactions
- Cascading failure scenarios
I think that I’ll stop here, but I am pretty sure that I can come up with a bigger list if I put my mind to it.
My questions about how did they come up with this architecture were mostly answered with: “that is how we do things” and “security”.
To add insult to injury, naturally the developers are running it all on a single machine, so they aren’t actually seeing what it going on there.
Here is how I would build such a system:
Note that I don’t have a Business tier or a Data tier, I have an Application tier. From my experience, even under fairly strict regulatory compliance rules, application servers can call the database, so the security aspect is covered. What we are actually doing, however, is a far more significant change.
We don’t need to worry about:
- lazy loading (NHibernate does it for us)
- change tracking (NHibernate does it for us)
- caching (NHibernate does it for us, mostly)
- reduced number of hops
- no need for distributed transactions
- reduced number of failure points
- have a chance to build a true domain model
As I mentioned before, I don’t even like the distinction between BAL and DAL, even when they are layers, instead of tiers. Trying to make them into tiers is going to cause quite a lot of pain. In essence, and the main thing that is being missed here, is that you are going to have to build some infrastructure to deal with the data at the Business tier. That may be just simple change tracking and DTC support, but it is likely that you’ll need more than that for real world applications. Caching and lazy loading are both topics that you’ll need to deal with, and neither is going to be an easy task.
This is what NHibernate is meant to do. People keep looking at NHibernate and seeing the Row <- -> Entity conversion, but that is just the very tip of a very big iceberg.
Most of the complexity within NHibernate is with things like caches, lazy loading and change tracking. That is where you are going to see the really significant time and complexity saving.
When you are forcing an architecture into that mode, you are basically removing a lot of the functionality already in the box, and forcing yourself to create it from scratch.
Instead of castrating your abilities, make sure that your architecture matches them, don’t play to your weaknesses, play to your strengths.
Comments
Ayende, when we deal with WCF we have Data Contracts for DTO and at the same time we have domain model and entities in our App Server. Lets say we have a customer entity and a customer datacontract.
The fields of this classes will mostly be the same.
Do you suggest anyway to have them as separate classes?
Depends on how complex the business layer is really. If it is very complex then you might need to hide the structure of the domain classes behind an app layer; but the important thing is that you talk in application API (data transfer objects) rather than instances of domain classes.
The DTOs will not only have all required data loaded but will not have any properties there which aren't required for the specific use case; preventing someone from "just using the one that is already there to do something else" - which would run you into lazy load problems because the object is not connected to a session.
In the suggested solution, I'm assuming WCF is used for communication between the web server and the app server. Don't you run into some of the same problems with lazy loading and caching there?
Arild: He is meaning lazyload/caching from the App to database. (WCF at the App level, NHibernate used 'behind' the WCF interfaces)
@lexx, generally it's not required. Infact DTO's created would club many entities together. Some people seperated DTO from entities because of .NET attributes imposed by WCF but that also I guess is no longer mandatory with .NET 3.5 SP1. Infact I would suggest you to share the entire Entity layer assembly with your presentation which would help you consolidate your validation logic as well (unless you are in a SOA mode).
@Arlid, I agree with you. Normally a single server should suffice (and is best in terms of performance, etc.) unless your Security requirements mandate it.
__“that is how we do things” is number 4 practice of the guide "how to beat a dead horse" :)
but the guy may have its own truth, because once you start building SOA applications, you DO need some kind of seperation, although the picture portraied is really ridiculous.
I think it is harder to do with NHibernate (although it is an awesome framework, congratz to 2.1!) but there are some, like LLBLGen, which offers change tracking inside entities, thus serializing them and transfering on wire isn't such a serious problem.
And lazy loading is a dangerous thing this way or that way :)
Btw, I don't see solution either in your picture, how would you transfer "entity" from the UI layer back to your Application Server layer if not using services? How would you track changes? Just asking, we both know who's guru here :)
Care to elaborate more? btw EXCELLENT post, I would really welcome more on this topic!
@ Peter,
In case we have separate classes we will have to copy data transferred from DataContract to entity in order to manipulate entities in the App Server. This sounds like a duplicate code. Don't you agree?
@ Niraj,
Mostly agree. Are you using session per request on the App Server side? Have you tried this kind of architecture on the high loaded apps?
Just to clarify, you'd use WCF to expose an API from the Application Server to the Web Server and keep the web server solely for handling requests?
Thanks,
Mike
Peter,
In general, I agree about the DTO, but I am not sure that I agree with hiding the BL in any way.
The app tier tend to talk in DTOs externally, and entities internally.
Niraj,
No! Yuck, you don't want to share entities on the wire, see the stripper pattern post about why.
cowgaR,
NH does have support for detached entities, it is just not a recommended approach.
As for the image, I agree it is ridiculous.
I wouldn't pass entities around, the UI deals with DTOs, never entities. Also, note that we are talking about tiers vs. layers, that is an important distinction
@lexx,
Yes, I want them to be separate classes.
Moreover, I want different classes for different scenarios.
CustomerForOrder is different than CustomerForLogin
If you care about the code to convert it, use AutoMapper, but it is really not much of a problem.
@Arild,
You don't push entities on the wires, and you don't do anything with them in the UI layer anyway, so there is no problem.
@Mike,
That really depend on too many variables to answer, but ideally, yes.
The web server would be mostly HTML generation and request handling, mostly out of local cache. The app server would handle request that require data or BL, and send them to the local cache of the web server.
that's why I put entity in parenthesis... but as far as I know it doesn't matter if it is DTO or entity, I still need to track changes down...
so, using NHibernate, and DTO, what is the recomended approach in asp.net mvc layer to communicate with application layer if detatched entities are not recommended solution?
thanks for answer
If anything is going to castrate your architecture, it is running your business of a Windows Home Sever. :)
@lexx
You might like AutoMapper.
@Ayende.
The app layer is the business layer. Talks DTOs externally, and then either manipulates business domain classes directly or via services.
By "business layer" I mean "application API" - not sure if we are using the same terminology or not.
cowgaR,
Not really, a single request tend to do one thing, so you usually pack it into a DTO and send it to the backend, where it unpacks itself and do something meaningful.
The best description of that is from Udi:
www.udidahan.com/.../entity-framework-disconnec...
If you want to use NH builtin support for that, take a look at session.Merge
Peter,
See my post about the DAL why I don't really like the division into BL and DAL.
I consider it false way of doing thing. I would much rather have a single layer which handle BL and infrastructure bits around it.
@Ayende, I get the point though WCF serializes only public part by default. But this is becoming crazy. I have entities layer, then I have DTO, and then if I am using something like MVVM like will have one more ViewModel. Lot of duplication :(. Anyways I will take it up in my next project with UI validation implemented inside ViewModel properties.
One more query in your post of UI should talk to DAL, you have a query wrapping business logic & then high level components paginate it. But do you mean that I have to retrive all data in my App layer & throw a window of it to presentation?
@lexx, I had used stateless singleton WCF implementation & it worked well for me. And all the logic related to given data was consolidated in a single place & assembly was shared (no add service reference - just IChannelFactory interface)
Niraj,
Please note that I advocate this type of architecture for a multi tier applications only.
If it is just running on the same machine, things are different, but for multi tiers, yes, you ARE going to have to do something about it.
Ayende,
I would love to see more of your findings on WCF and NHibernate usage.
I recently started to use WCF and have been using nhibernate already for a fair amount of time. There seem to be several hoops which the combination of the two technologies make you jump through, like where to put the session factory etc.
Would you do a few demo apps or more blog posts for how you would recommend the two technologies working together? and where the common pitfalls are. I think this would make the NHibernate and WCF combination clearer to people, it would also help my understanding out immensely.
Thanks
Pete
I think its better to have some demos of N-Tier best practices of "WCF and NHibernate" rather than talking in air.
Ayende,
Niraj,
Actually, I don't mind overly much if you do the controller stuff directly in the WCF service, no.
Some people just don't understand the difference between layers and tiers. Using a layered architecture is generally good, but that doesn't mean you need to run them on separate tiers (and in fact, it causes problems with performance and more). I've seen this problem before; I hope you sorted them out.
Comment preview