The right dependency level

time to read 6 min | 1096 words

This post was prompt by an email that was

I've read many things on forums regarding the architecture of an ASP.NET 2.0 application which is using NHibernate. Personally I like most the idea with service module (not necessary web service, just an assembly with the role of a service). The main idea for me is to make the application independent of the NH.
Therefore I consider that app should be aware only of the Model, Service.

My main concern is how can I make this service to force NH to be transparent for the main application. What I mean with this.
Some of the methods from this assembly, that will return Model objects, will use directly HQL (these ones are no problem).
But I will need for sure a wrapper over the NH session, in this way NH will became unknown to the main project.

The problem in creating this wrapper.
Some of the methods are very simple...for example Delete, Save, GetById.
The main problem is on creating the methods for retrieving the data based on a list of conditions.

To Most of everyone agree that having something like new SqlConnection() in your business level code is not a good idea. Why then people have much less trouble with code such as this?

IList<Customer> customers = 
session.CreateCriteria(typeof(Customer)) .List<Customer>(); //process customers

Yes, this is not a valid example, because this may be a sign that you are mixing your concerns here, but bear with me here, I am not going to build a 9 layer application just for demonstration purposes. If we frown on using a SqlConnection, why is using the session much more acceptable? Isn't something like this preferred?

public IList<Customer> GetCustomers()
	// do query here	

Well, it is certainly much more explicit than the previous example, isn't it? The problem that I have with the code above is that it usually turn to something like:

public IList<Customer> GetCustomers()
	using(ISession session = factory.OpenSession())
		//transaction handling omitted for brevity
	 	return session.CreateCriteria(typeof(Customer))

The problem with that is that it misses out on one of the more important features of the platform, the Unit of Work implementation. It relegate the OR/M into a stupid tool getting/saving data from a persistence store. So, then you take the next step, and create something like IUnitOfWork, and abstract the Unit of Work implementation from the rest of the application. Problem solved, right?

Well, no, it isn't. It is only "solved" for the very simple case. What happens when you want to do a more complex query? Or utilize features of the underlying OR/M to your best advantage.

I am going to pick LLBLGen and NHibernate as two OR/M products that have roughly similar capabilities, but radically different design. LLBLGen doesn't use the Unit of Work pattern, for instance, so the abstraction above it fairly useless in this scenario. A lot of the stuff that you would do in one tool would be actively harmful in another. Trying to build an application in a way that would take advantage of only the basic features of the tool that you are using is a big disservice for the application, it complicate the programming model, and it wouldn't gain you much flexibility in the end.

In my Rhino Commons library, I have two implementations of the IUnitOfWork interface, one for NHibernate, and the second for Active Record, which is NHibernate. I have explicitly made the choice to being able to take full advantage on the capabilities of NHibernate, which means that I can't take the same code base and try to use that against another OR/M, even one that is using the Unit of Work pattern.

NHibernate is explicitly designed to be used in a way that doesn't force a dependency between the domain model and NHibernate, but trying to abstract NHibernate from the service layer is a losing proposition. The service layer now does a translation from the NHibernate's concepts to terms that are really NHibernate's terms, but in the service layer's namespace. You can't port this code:

   new Predicate(Operator.Equality, "City", "London"

As seen above, this is usually more awkward than the original syntax.

To be clear, this doesn't mean that you can now freely scatter code all over the place that uses NHibernate directly. SoC is still king, but you should accept that trying to replace a core part of the system such as NHibernate is going to be a very costly endeavor. I have seen a few efforts where people have decided that they want to use NHibernate, but without taking a dependency on it. It was costly, confusing, and eventually abandoned, because the translation layer always had this one more feature that a developer needed that NHibernate supported. And adding this feature to the translation layer would have created 1:1 mapping between NHibernate concepts and the service layer concepts, making the whole effort pointless.

That approach also meant that you couldn't take advantage of NHibernate's tools such as NHibernate Query Generator, which can really make the syntax smooth.

What do I recommend, then?

  • Evaluate the various OR/M implementations, and find out what fits your scenarios best.
  • Make full use of all the OR/M capabilities, don't limit yourself to a strict subset because in the future you may want to replace it.
  • If independence from the OR/M is important to you, understand that no matter how you try, you would still have some dependencies, usually in a place that you wouldn't look for, and that you will likely spend a lot of time on translation layer needlessly.
  • However, in that case, be sure to create a service layer that can express what you want to do in the terms of the domain.

What do I mean by that?

Instead of:

  .Add(Expression.Eq("Name", london))

Or even this:

FindAll<Customer>(Where.Customer.City == london);

Which is fairly clear to read even if you are not really familiar with the syntax, try to do this:

FindAll<Customer>(  CustomerByCityFinder.With(london) );

This is an approach that can help isolate you from the complexity of the OR/M layer, but it means that you now separate the place where you do the query from the query itself. This is not a bad thing on itself, except that this is already done for you by the OR/M itself. Using NHQG, those queries are very expressive in the terms of the domain.

I reserve this approach for the more complex queries. Where the query is complex enough to have its own responsibilities.