Alternative to repository pattern: query objects using Castle Windsor
Monday, May 16, 2011 at 23:42 Repository is one of the most popular patterns.
It is used in almost every project that has to deal with persistent data. But I don't like this pattern because it tends to break the single responsibility principle and accumulate a lot of methods like GetById, GetByName, GetByAnotherProperty and so forth (here I'm not talking about repository pattern in event sourcing architecture where it usually has only couple methods - GetById and Save). I didn't even mention repository tests - size of test classes grows much faster and then testing becomes nightmare.
From this perspective I like the query object pattern much more because it combines ultimate simplicity with incredible flexibility. In this post I'll show you how I use this pattern by the instrumentality of Castle Windsor IoC container and its awesome feature called Typed Factory Facility.
So, our queries implement this interface:
public interface IQuery<in TQueryContext, out TResult>
{
TResult Execute(TQueryContext context);
}
As you can see it contains only one method Execute that has input parameter TContext and output parameter TResult. So all implementers of this interface take bunch of input parameters grouped into the context and produce result based on theirs internal logic.
Lets take a look at example of the class that implements this interface.
public class GetWelcomeMessageQuery : IQuery<GetWelcomeMessageQueryContext, Message>
{
//Collection is just your data storage. You can inject here EF's DataContext or NHibernate's ISession
private readonly Dictionary<int, Message> collection;
public GetWelcomeMessageQuery()
{
var message1 = new Message(1, "Hello, world!");
var message2 = new Message(2, "Welcome to ASP.NET MVC!");
var message3 = new Message(3, "Hi, dude!");
collection = new Dictionary<int, Message>
{
{message1.Id, message1},
{message2.Id, message2},
{message3.Id, message3}
};
}
public Message Execute(GetWelcomeMessageQueryContext context)
{
if(collection.ContainsKey(context.MessageId))
return collection[context.MessageId];
return null;
}
}
As you can see, this class uses GetWelcomeMessageQueryContext as context, which contains only one parameter – messageId, and returns single Message.
Usage of this class is quite straightforward but elegant and based on Typed Factory Facility as I mentioned above.
public ActionResult Index()
{
int messageId = new Random().Next(1, 4);
var context = new GetWelcomeMessageQueryContext {MessageId = messageId};
Message message = Query<Message>().SingleOrDefault(context);
ViewBag.Message = message.Text;
return View();
}
Query<T>() method simply creates QueryBuilder that holds reference to IQueryFactory and uses it for instantiating query classes with container.
public class QueryBuilder<TResult>
{
private readonly IQueryFactory queryFactory;
public QueryBuilder(IQueryFactory queryFactory)
{
if(queryFactory == null)
throw new ArgumentNullException("queryFactory");
this.queryFactory = queryFactory;
}
public TResult SingleOrDefault<TContext>(TContext context)
{
var query = queryFactory.Create<TContext, TResult>();
return query.Execute(context);
}
public IEnumerable<TResult> Enumerable<TContext>(TContext context)
{
var query = queryFactory.Create<TContext, IEnumerable<TResult>>();
return query.Execute(context);
}
}
public interface IQueryFactory
{
IQuery<TQueryContext, TResult> Create<TQueryContext, TResult>();
}
As shown in code above, IQueryFactory has only one method Create() that “pulls” queries from container and does not have void method Release() for releasing queries. Releasing components it doing using Castle Dynamic Proxy's interceptors. In code below you can see how to enable interceptors for purpose of releasing queries instances. Implementation of AutoReleaseQueryInterceptor can be found in the code corresponding to this post.
public class QueriesInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { var queries = AllTypes.FromAssemblyContaining<GetWelcomeMessageQuery>() .BasedOn(typeof (IQuery<,>)) .WithService.FirstInterface() .Configure(x => x.LifeStyle.Transient .Interceptors<AutoReleaseQueryInterceptor>()); container.Register(queries); container.Register(Component.For<IQueryFactory>().AsFactory()); container.Register(Component.For<AutoReleaseQueryInterceptor>()); } }
One of huge advantages of this pattern is its ultimate flexibility - you can easily implement such cross-cutting concerns as caching or logging. For example, you can derive you query class from CachedQuery and result will be cached according cache strategy you use.
public abstract class CachedQuery<TContext, TResult> : IQuery<TContext, TResult>
where TContext : ICacheContext
{
private readonly ICacheStrategy cacheStrategy;
protected CachedQuery(ICacheStrategy cacheStrategy)
{
this.cacheStrategy = cacheStrategy;
}
public TResult Execute(TContext context)
{
string cacheKey = context.GetCacheKey();
object fromCache = cacheStrategy.Get(cacheKey);
if(fromCache != null)
return (TResult) fromCache;
TResult result = ExecuteCore(context);
cacheStrategy.Put(cacheKey, result);
return result;
}
protected abstract TResult ExecuteCore(TContext context);
}
Of course, as any solution, this one has own disadvantages:
- count of classes grows much faster than if you were using repository pattern;
- this approach uses convention which kind of hard to test.
First one for me is not real disadvantage cause all classes are very simple and have only one responsibility, but second one is quite significant. Meanwhile, I think good points of the query object pattern outweigh its disadvantages, so I will keep on using it.
Code from this post you can find in xelibrion/Lab repository on github.

Reader Comments