views:

94

answers:

3

Given:

  • You have an architecture with the layers presentation, business and data.
  • You are applying domain-driven design.
  • You are using an object-relational mapper that lets you create object-oriented queries (e.g., NHibernate which lets you create HQL queries).

Question:

Into which layer should you put the object-oriented queries?

My thoughts:

I think putting them into the presentation layer will usually make no sense. But I don't know whether to put them into business or data layer.

Example (NHibernate):

Say your business logic needs some method GetCustomersPossiblyInterestedIn(Product p). You might then create a complex HQL query that selects customer objects where the customer has already bought a product that is in the same category as p.

Argument a)

On the one hand, I'd say this query is clearly business logic, because the decision that a customer is treated as possibly interested in a product based on whether she has bought a product in the same category is a business decision. You might as well select customers who have bought multiple products in the same category with a similar price, e.g.

Argument b)

On the other hand, the business layer should not depend upon the data layer, so directly using NHibernate in the business layer rings an alarm bell.

Possible solution 1)

Create your own object-oriented query language that you use in the business layer and translate to HQL in the data layer. I think this would cause a lot of overhead. If you use a query language based on query objects instead of a parsed query language, you could probably safe some effort, but my objection still applies.

Possible solution 2)

Directly using NHibernate in the business layer is O.K., because the abstraction level that NHibernate is able to provide with HQL, ISession etc. fits the business layer. There is no need to wrap it.

What do you think?

Edits:

See "Repository is the new Singleton" and "The false myth of encapsulating data access in the DAL" by Ayende Rahien for closely related discussions.

A: 

I prefer to wrap NHibernate (or other ORM) using some sort of a Repository. For eg. in the case of NHibernate the Repository would wrap the NHibernate session. This repository could be per class (So CustomerRepository and OrderRepository) or if you don't have too many classes in your domain, you could start with a single Repository as well.

This is then the perfect place to put Criteria queries (LoadAllByName, LoadCustomerWithOrder, etc.). If you later need to switch to a different ORM or even a different persistence mechanism (very rare, I would think), you can swap out the whole Data layer including the Repository.

Rohith
A: 

Queries belong in the repository. You may have a function signature for GetCustomersPossiblyInterestedIn(Product p) anywhere it is necessary, but the query itself should only be in a repository class

Steven A. Lowe
A: 

Definitely, avoid putting object-oriented queries into presentation layer. It should ONLY display/use data received from business logic layer (BLL). Without any querying. If you need to query results received from you BLL, then your BLL needs to be extended to provided such data that don't need to be queried.

Your idea to use 'object oriented query language' seams like a good. Usually, this "language' is your DAL :) I would say good example of "object oriented query language" is a properly implemented Data Access Layer (DAL).

From my perspective you DAL should implement 80-90% of all functionality and provide set of functions like that:

  • Customer GetCustomerById(int customerId);
  • List GetLastRegisteredCustomers(int count);
  • etc...

These functions provide the biggest part of required functionality that don't need to be queried.

For all other 10-20% of rarely used queries (you named them "object oriented") your DAL should implement method/methods that return IQueryable result, I would say at least 'GetAll()' method and probably few customizations:

  • IQueryable GetAll();
  • IQueryable GetCustomerByCountry(int countryId);

in this case if you will need to find a customer in a country registered this year on you BLL you will call:

List<Customer> customers = GetCustomerRepository()
    .GetCustomerByCountry(countryId)
    .Where(customer=>customer.RegisterDate.Year==year)
    .ToList<Customer>()
    ;

Guess, you know what provide IQueryable<> interface.

How to work with Linq under NHibernate: http://stackoverflow.com/questions/624609/linq-to-nhibernate.

One additional hint: I would recommend to use 'Repository' pattern for you DAL implementation. Some time ago i used this for general idea: http://habrahabr.ru/blogs/net/52173/ (if you can't read Russian translate whole page with google - it should be readable).

Hope that helps.

Budda
So, you would split complex queries between DAL and BAL?I am not too familiar with Linq/ Linq to NHibernate. What would GetCustomerByCountry(int) return? All customer objects in-memory?
Marco
var customersByCountry = "GetCustomerByCountry(int)" will return a "query-object". This is an expression that will be executed by DATABASE WHEN you call something like customersByCountry.Fisrt(), customersByCountry.ToList(), etc. But BEFORE you call .ToList() you could call 'Where' extension method which will apply additional conditions on your query that will be executed by DB also. I would recommend to use your DB profiler to see what and when is really executed to have better understanding what is going on. Let me know if you have more questions, but that could be another topic. GL!
Budda
Thanks! Assume you could not use Linq (e.g., in Java), how could you accomplish something similar? Is there a named design pattern that is implemented by the Linq to NHibernate approach you describe?
Marco
Sorry, in this case I would ask to Java experts: "How to implement 'delayed' query execution" (sorry, I am not expert on Java, just good a little in .NET). Pretty sure answer should exists (otherwise, Java lost 'holy-war' against .NET)
Budda
"Something similar" you could implement as method that calls any StoredProcedure (GetCustomersByCountry) with all required parameters (CustomerId, etc). And one additional string parameter which will be passed into this procedure. Inside, if parameter is not empty it should be joined to the 'WHERE condition' (if you need to join additional table - pass one more parameter for this table name). You also need to implement a 'factory' class that will provide you with such set of 'additional parameter'. This patter is described by Fowler: ... don't remember and can't find the pattern name.
Budda
The most close pattern is "Query Object", book "Patterns of Enterprise Application Architecture". Suggested implementation doesn't take into account presence of 'IQueryable' interface. But it gives you a general idea.
Budda