Thinking about APIs: Advance Usages

time to read 9 min | 1692 words

You may have noticed that I have create some wrappers around NHibernate, to make it easier to work with. NHibernate's API is very rich, and it can get pretty long if you are not careful. Consider the following:

Session.CreateCriteria(typeof(Post))

      .Add(Expression.Gt("PostedAt", DateTime.Now.AddDays(-3)))

      .Add(Expression.Eq("ShowInMainPage", true))

      .AddOrder(Order.Desc("PostedAt"))

      .SetFirstResult(0)

      .SetMaxResults(25)

      .List();

The above is very clear way, if long winded, to say "give me the last 25 posts in the last 3 days that should be shown in the main page". I swim around this API like fish in the water, until it comes to testing.

Trying to mock the above sentence is a PITA of the first order, and I say this as someone who wrote a mocking library :-)

Partly because of this, partly because I liked the API from Active Record, partly because I wanted a mode DDD model to work with, and partly because a client wanted to avoid commitment to a specific OR/M, I created a wrapper around it that gave some very nice features, adding code generation didn't hurt, so the above query now looks like this:

Repository<Post>.FindAll(0,25,Order.Desc("PostedAt"),

                         Where.Post.PostedAt.Gt(DateTime.Now.AddDays(-3)),

                         Where.Post.ShowInMainPage.Eq(true));

Using the Repository<T> is very simple, you can check the API here:

(Image from clipboard).png

Most of the saving are done by removing the fluent interface, and while I feel that this is simpler in most cases, there are some things that it just can't express.

I have talked before about cachable queries, in NHibernate, the are expressed by calling SetCachable(true), and setting the Cache Region, if neccecary. My API doesn't allow for it. Oh, I could add another parameter, but I already have a possible parameter explosion (check out the number of overloads on FindAll() ), and going this route would force me to add a parameter that would force bypassing the cache, etc.

Clearly, NHibernate's approach is much more reasonable now, it is easily extensible to support more options, and it doesn't break down when the complexity goes up.

I really like the fact that I have intellisense at my hands, and that I don't have to scroll through the 50+ methods (I counted) that some of the interfaces in NHibernate have. In addition to that, I have a problem where I want to cache a particular query, part of the time, and only for a very specific part of the time. In the part in question the queryis called roughly 3,650,000 times during the process of executing a certain web request [three points to the first fellow who can tell me what I'm doing]. Obviously going to the database per each query isn't going to be worth my time.

This query is sitting very low in the stack, and I really don't want to have to change / modify it in place, and I don't really like the idea of a caching version vs. non-caching version. After getting up and hitting the wall a couple of time with someone else head (What, you expected me to use mine? I use it for thinking. :-) ), I came up with the following solution:

(Image from clipboard).png

The usage is fairly simple:

using(With.QueryCache())

{

      ICollection<Post> postsToShow = Repository<Post>.FindAll(0, 25, Order.Desc("PostedAt"),

         Where.Post.PostedAt.Gt(DateTime.Now.AddDays(-3)),

         Where.Post.ShowInMainPage.Eq(true));

}

I'll be the first to admit that the "using(With ..." syntax is not the best there is, but in this case, I don't need to special case the exception case, so I don't think that I need to use a function that takes a delegate here.

The really interesting part is the TemporaryQueryCache(), which will enable the query cache for the duration of the using() statement. This means that I can selectively turn it on/off for a part of the code that require caching without actually modifying any code lower in the stack. This enables me to make much better decisions with regard to how and when I can use caching.

I commited my changes, but they are still in the proof of concept stage, and may change.

You can check them out here, or check out the entire tree using:

svn co http://svn.berlios.de/svnroot/repos/rhino-mocks/trunk/rhino-commons