Query Building In The Domain / Service Layers
Here is an interesting topic. My ideal data access pattern means that there is a single query per controller per request (a request may involve several controllers, though). That is for reading data, obviously, for writing, I will batch the calls if needed. I am making heavy use of the Multi Criteria/Query in order to make this happen.
I have run into a snug with this approach, however. The problem is that some of the services also do data access*. So I may call to the authorization service to supply me with the current user and its customer, and the service will call the DB to find out about this information. I would rather that it would not do that, since that means extra round trips. Now, NHibernate has the idea of a DetachedCriteria, a query that has semi independent life, can be constructed at will and massaged to the right shape anywhere in the code.
Now, I have the following options. Use the normal method:
ICollection<Customer> customers = AuthorizationService.GetAssociatedCustomers(); ICollection<Policy> policies = Repository<Policy>.FindAll( Where.Policy.Customer.In(customers) ); PropertyBag["customers"] = customers; PropertyBag["policies"] = policies;
Use DetachedCriteria as a first level building block:
DetachedCriteria customersCriteria = AuthorizationService.GetAssociatedCustomersQuery(); IList results = session.CreateMultiCriteria() .Add(customersCriteria) .Add(DetachedCriteria.For<Policy>() .Add( Subqueries.PropertyIn("id", CriteriaTransformer.Clone(customersCriteria)
.SetProjection(Projections.Id())
) ) ).List(); ICollection<Customer> customers = Collection.ToArray<Customer>(results[0]); ICollection<Policy> policies = Collection.ToArray<Policy>(results[1]); PropertyBag["customers"] = customers; PropertyBag["policies"] = policies;
Remember, I consider Querying a Business Concern, so I like the ability to move the query itself around.
Thoughts?
* By data access, I mean, they call something that eventually resolves in a DB call.
Comments
Funny, I have been thinking about the exact same topic.
“The single query per controller per request”, is more like a single query per user story step.
A simple scenario…
User story: Add Item to existing Order
Steps:
1 – The user opens an existing order
2 – The user selects an orderline and lookup an item.
3 - The user selects an item from the lookuplist and is return to the order. Where the orderline has been updated with the item.
In the ideal world this would be 3 queries and 1 update.
However that’s far from how we do it today.
What we do now is something like:
1 – Behaviour
1.1 – Query
2 – Update
So every query is nested under the behaviour that’s needs it.
In order to only do 1 query the only way I could think of doing this, is with serial phase logic:
Something like:
1 – Query registration phase
2 – Query phase
3 – Behaviour phase
4 – Update phase
Every behaviour element must be able to inform what query it needs. This way the 1 of each type of query can be batched together in a single request to the DB. After that the behaviour operates on the result of the query, and can finally update if needed.
This is of cause not a silver bullet, there are problem areas:
Every behaviour now has to inform what queries it uses. Perhaps this could be solved with attributes or AOP?
If the behaviour uses the query data for a logical branch. Ex: If customer gold navigate to special priced goods else navigate to homepage. In this case the query preparation must include both the special priced goods and the data for the homepage, because the behaviour does not yet know if the customer is gold.
That’s how far I got…
If I understand this correctly, will that code result in one trip to the server where 2 queries are executed like this?
"select * from customers where blah = something"
"select * from policies where customer_id in ( select id from customers where blah = something)"
I personally like the idea where a query in multicriteria can be made to use the results of a cloned query, it's neat. I've not used detatched queries but I can't wait to, I often see query construction and execution as separate concerns so they fit nicely. The fact this is all happening in one round trip is great too.
The 1st code sample is short, simple and easily understood. The 2nd option is much less explicit though. It took me about 4 reads to get a feel for the code, possibly because there are many more additional concepts involved:
Deatched Queries
Multi Criteria Queries
Projection
Object Cloning
I don't use these in my applications yet, which could be why they're a little overwhelming for me. This might be clearer though if it were possible?
DetatchedCriteria<Customer> customersQuery = AuthorizationService.GetAssociatedCustomersQuery();
DetatchedCriteria<Policy> policiesQuery = Repository<Policy>.DetatchedFindAll(
);
results = session.CreateMultiCriteria( customersQuery, policiesQuery ).List()
ICollection<Customer> customers = results[customersQuery];
ICollection<Policy> policies = results[policiesQuery];
Tobin,
The second way execute it in a single round trip.
Yes, the second query is less explicit initially, but you learn to grok that fairly quickly.
The syntax that you suggest is interesting, and certainly possible.
Nice code!!!
In my view it`s very interesting and I would be more deeply investigate it, but some pieces of code isn't understandable for me... :-(
for example what is the best practicies for using this construction?
DetatchedCriteria<Policy>
It`s kind of some repository or I make a mistake ?!
DetachedCriteria.For<Policy> is actually a way to specify a query without the need to have the session available.
I'm envisioning a per request query registry of some sort where objects can register the query they need run with a callback. The registry will make a single mass, multi-criteria call to the server, then notify each registrant that their results are available....oh the possibilities.
What I am doing right now is a base class method that gathers all the queries, and then get them back afterward. The registry is a bit too complex for my needs right now.
But yes, the queries can get fairly massive ;-)
The registration stuff is the first thing that came to my mind after reading the post too.
The registration phase would be as easy as just writing the queries you want... they'd all return proxies. The proxies would be smart enough to know what their original query was so that things like Where.Policy.Customer.In(customers) would build a query w/ a subselect.
In other words your first example code would work just fine and only do a single query the first time one of the entities was accessed (customers[0] would exec that multiquery)
Just imagine if it was combined w/ Adaptive Fetching Strategies (http://blog.eleutian.com/2007/06/01/AdaptiveFetchingStrategiesInORMs.aspx)
Every query in every controller would have a strategy... oh what a beautiful world that would be.
Do you really want to run queries that may be potentially on behalf of unauthorized users? It's not like you can describe any data or policy dependencies among the queries you put in the MultiCriteria so there's a limit to how far this can be taken.
Really I'd like to see this kind of thing happen with a proper data-flow oriented language where data dependencies can be clearly traced and opportunities for data parallelism exploited to the fullest extent without a whole lot of hand tuning.
@Aaron Jensen, the registration phase you're describing reminds me of Futures.
The registration phase is like a Unit of Work for queries.
Unit of work registers “updates”, to be simultaneously updated.
Query Unit of Work registers “reads”, to be simultaneously updated.
Only the problem how do you avoid loading to much, when with a lot of logical branching in the behaviour. Perhaps there is a need to map Simple OO behaviour to Query language?. Does that sound a lot like LINQ?
So OO behaviour can state a logical branch for queries, like : Register in Query Unit of Work -> “Get Special prices goods if customer is gold else Get Homepage data”, and the Query Unit of Work would automagically do the check for “customer is gold”, in the query.
This would be fairly easy with simple data (fieldvalue or sum etc.), but more complex (nested) scenarios would be hard to make?
Also this would probably hurt scalability, since we now do our conditional logic in the database?
Whoops: C/P error ;-)
Unit of work registers “updates”, to be simultaneously updated.
Query Unit of Work registers “reads”, to be simultaneously updated.
Should read:
Unit of work registers “updates”, to be simultaneously executed.
Query Unit of Work registers “reads”, to be simultaneously executed.
Can you give me the use case that would require this branching of logic?
@Ayende
"Can you give me the use case that would require this branching of logic? "
I have a blog post which discusses these issues in the context of lazy loading.
http://www.matshelander.com/wordpress/?p=45
I try to describe examples of branching logic in it.
My conclusion is that it is not really feasible to try to load everything up front if you also want to build well factored and reusable code.
/Mats
Comment preview