Monday
May162011

Alternative to repository pattern: query objects using Castle Windsor  

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:

  1. count of classes grows much faster than if you were using repository pattern;
  2. 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.

References (3)

References allow you to track sources for this article, as well as articles that were written in response to this article.
  • Response
    (coral), this supple leather clutch is perfect for a nice day out with friends. Raw edge detailed supple and soft leather with goldtone hardware Zip close raised front
  • Response
    Response: OSYHOjJZ
    Blog - Be SOLID - Alternative to repository pattern: query objects using Castle Windsor  
  • Response
    Response: FgUwqIHk
    Blog - Be SOLID - Alternative to repository pattern: query objects using Castle Windsor  

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>