The DAL should go all the way to UI

time to read 4 min | 675 words

My recent post caused quite a few comments, many of them about two major topics. The first is the mockability of my approach and the second is regarding the separation of layers. This post is about the second concern.

A typical application architecture usually looks like this:

image

 

This venerable structure is almost sacred for many people. It is also, incidentally, wrong.

The main problem is that the data access concerns don’t end up in the business layer. There are presentation concerns that affect that as well. Let us take a look at a common example. I want to show the invoices for a user:

image

Given that requirement, I quickly build my interface, implement it and throw it over the wall to the UI team to deal with it*.

Here is the interface that I came up with:

  • GetInvoicesForUser(userId)

Great, isn’t it? It satisfy all the business requirements that I have.

Except, my UI can’t actually work with this. We have to have paging there as well, and the only way to do paging using this API is to do that on the client side, which is probably bad. I grumble a little bit but change the interface to:

  • GetInvoicesForUser(userId, pageNum, pageSize)

Done, right?

Well, not really. I have a new UI requirement now, the user want to be able to sort the grid by any column. Now I grumble even more, because this is harder, but I create the following interface:

  • GetInvoicesForUser(userId, pageNum, pageSize, orderByCol, orderByDesc)

And then they want to order by multiple columns, and then they…

Do you notice a theme here? A lot of the data access concerns that I have are not actually concerns that the layer above me has, they are one layer removed.

But there is a more important problem here, I am violating (repeatedly) the Open Closed Principle. As a reminder:

software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

My data access is not open for extension, and it is certainly not closed for modification.

The problem is that I am trying to create a hard isolation between two pieces of the system that need to work very closely together, and as long as we are going to insist on this strict separation, we are going to have problems.

My current approach is quite different. I define queries, which contains the actual business logic for the query, but I pass that query onward to higher levels of my application. Any additional processing on the query (projections, paging, sorting, etc) can be done. In that way, my queries are closed for modification and open for extension, since they can be further manipulated by higher level code.

A good example of that would be:

public class LatePayingCustomerQuery
{
	public DateTime? DueDate {get;set;}
	public decimal? MinAmount {get;set;}
	public deciaml MaxAmount {get;set;}
	
	public IQueryable<Customer> GetQuery(ISession session)
	{
		var payments = 	from payment in s.Linq<Payment>()
			 		where payment.IsLate
					select payment;
	
		if(DueDate != null)
			payments = payments.Where( payment => payment.DueDate >= DueDate.Value );
			
		if(MinAmount != null)
			payments = payments.Where( payment => payment.Amount >= MinAmount.Value );
		
		if(MaxAmount != null)
			payments = payments.Where( payment => payment.Amount <= MaxAmount.Value );
		
		return 	from customer in s.Linq<Customer>
				where payments.Any( payment => customer == payment.Customer )
				select customer;
	}
}

I hope that this clears the air a bit about how I approach data access using NHibernate.

* it is a joke, nitpickers will be shot.