Skip to content

Query Objects instead of Repositories

Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


QueryThe repository pattern is often used to encapsulate simple and sometimes rather complex query logic.   However, it has also been morphed into handling persistence and is often used as another layer of abstraction from your data mapping layer.   This blog post show you how to slim down and simplify your repositories or possibly eliminate them all together by using query objects. A typical repository will look something like this:
public interface IProductRepository
{
	void Insert(Product product);
	void Delete(Product product);
	IEnumerable<Product> GetById(Guid id);
	IEnumerable<Product> GetAllActive();
	IEnumerable<Product> FindByName(string name);
	IEnumerable<Product> FindBySku(string name);
	IEnumerable<Product> Find(string keyword, int limit, int page);
	IEnumerable<Product> GetRelated(Guid id);
}
Each of the Get/Find methods implemented above would encapsulate a specific a query.  This type of repository would most likely be used to then transform the data returned from a set of methods into a View Model which would then be passed to our view or serialized and sent back to the caller (browser).  As an example of passing the model to a view in ASP.NET MVC, it would look something like this:
public ViewResult ProductDetails(Guid productId)
{
	var product = _productRepository.GetById(productId);
	var relatedProducts = _productRepository.GetRelated(productId);
	
	var model = new ProductDetailsModel
	{
		Id = product.Id,
		Name = product.Name,
		Price = product.Price,
		PriceFormatted = product.Price.ToString("C"),
		RecommendedProducts = (from x in relatedProducts select new ProductDetailModel.RecommendedProducts {
				ProductId = x.RecommendedProductId,
				Name = x.Name,
				Price = x.Price,
				PriceFormatted = x.Price.ToString("C")
			})
	};

	return View(model);
}
As I’ve mentioned before, I believe you should think of your MVC framework as an HTTP interface to your application.  Regardless if you are returning HTML or JSON, the generation of your View Model should not be coupled to your MVC framework.

Query Objects

In the example above, I want to extract the generation of my view model info a query object.  A query object is similar to a command object for processing behavior in our domain.  Our query object will now look like this:
public class ProductDetailsQuery
{
	public Guid ProductID { get; private set; }
	
	public ProductDetailQuery(Guid productId)
	{
		ProductId = productId
	}
}
In order to execute our query, we will implement in a query handler.
public class ProductDetailQueryHandler
{
	private DbContext _db;
	
	public ProductDetailQueryHandler(DbContext db)
	{
		_db = db;
	}
	
	public ProductDetailModel Handle(ProductDetailQuery query)
	{
		var product = (from p in _db.Products where p.ProductId == query.ProductId).SingleOrDefault();
		if (product == null) {
			throw new InvalidOperationException("Product does not exist.");
		}
		
		var relatedProducts = (from p in _db.RecommendedProducts where p.PrimaryProductId == query.ProductId);
		
		return new ProductDetailsModel
		{
			Id = product.Id,
			Name = product.Name,
			Price = product.Price,
			PriceFormatted = product.Price.ToString("C"),
			RecommendedProducts = (from x in relatedProducts select new ProductDetailModel.RecommendedProducts {
				ProductId = x.RecommendedProductId,
				Name = x.Name,
				Price = x.Price,
				PriceFormatted = x.Price.ToString("C")
			})
		};
	}
}
Now we have encapsulated the generation of our view model which can be used in our controller.
public ViewResult ProductDetails(ProductDetailQuery query)
{
	var model = _queryHandler(query);
	return View(model);
}
Now our controller is responsible for delegating the call to generate the model and action result or serialization.  In my next post I will take this a step further by introducing a common interface for our query handler in order to accept multiple query objects and  return types.

8 thoughts on “Query Objects instead of Repositories”

  1. Pingback: Thin Controllers with CQRS and MediatR

  2. Pingback: Identify Commands & Events - CodeOpinion

  3. Pingback: Validating Commands

  4. but how is this different from having not one but many specialized repositories? is it a good idea to use query object directly in controller? shouldn’t it be some service object which used this query object?

    1. Basically you are on the correct track in that each query object is basically a specialized, narrow and specific repository. The benefits are that because it has high cohesion with it’s dependencies, that means per query object you only have the dependencies you actually need. I would be calling a query from my controller using the mediator pattern to decouple from the actual implementation. Check our my Fat Controller CQRS Diet series for more on that: https://codeopinion.com/fat-controller-cqrs-diet/

      1. I’m struggling a bit to see the killer reason for using Query objects over repository. I can see the benefits of CQS but it’s this command pattern type implementation of the Q part I struggle with. I can see for Commands (mutators) the benefits of having separate handlers as you can use decorator pattern to wrap them with additional functionality e.g. Transaction, Audit etc.

        However for queries I’m struggling to see why you wouldn’t just use a normal repository. Essentially your IQuery object defined what would be a method signature in the repository and the handler defines the implementation of the method. However at some point you have to compose the IQuery class to it’s handler either using a dependency injection framework or Mediator pattern as in your following blog.

        I’m just not sure what the advantage is really, taking your point above about it only having the dependencies it needs, but in reality be it Repository or Query object you’ll be passing the same dependency of DbContext or similar.

        It just feels like re-parcelling the same code in a different way and ultimately is going to rely on folder or namespace hierarchies or exceptionally good class naming by developers to keep the inevitable class explosion in some kind of order.

        Not being critical BTW just would really value your opinion. I’m due to be taking on an externally developed prototype system which uses query objects as per your post soon and would like to be able to explain to my team (who are used to Repository) why we should keep this new way of doing things.

        1. Awesome comment/questions! Thank you for taking the time to post this.

          Since the writing of this blog, I still use both repositories and query objects. I don’t exclusively use one or the other.

          I use query objects primarily when I need a pipeline and as a public API (outside my core). I generally use them from a controller to generate viewmodels that would be serialized out by my web framework (Nancy) to the browser/client. In terms of the pipeline, I do this mainly when I want to do authorization, validation and logging. Which is basically the same scenario for the command side. So for those scenarios the command pattern on the Query side works well (for me).

          I use very narrow repositories more for internals within the core of my code. They are never used outside the core of my application, eg in a controller or in another context. Because of this, I don’t require the authorization, logging etc.

          Hope this answers you question.

          1. Excellent, thank for the reply. Your answer makes sense. I couldn’t understand the use of the GoF command pattern for the Queries as it’s a design to encapsulate invocation and is therefore usually used to defer execution which typically when issuing a query you don’t need as you tend to need the result there and then. But using it within a pipeline makes sense so that you can feed it into the pipeline and either decorate or defer until other tasks such as you mentioned are complete.

            As with most things think we’ll have to consider its use on a system by system basis. Anyway, once again thanks for the reply, appreciate it.

Leave a Reply

Your email address will not be published. Required fields are marked *