Saturday, November 10, 2012

CQRS Query Engine - Writing a Testable, maintainable and extensible CQRS Query Engine

  1. Designing the Query Engine
  2. Encapsulating common behavior (template pattern)
  3. Implementing Queries
  4. Creating the Query Resolver

In my last post I blogged about writing a Query Repository in a test-friendly way but even when the code was testable I still believe it can be improved in several ways.


I’m going to build a small, simple and test-friendly Query Engine. This will be the first post in this series.


If you are familiar with CQRS you will find the code of this post familiar, since I’m following the same patterns used in the Commands branch in a CQRS architecture


The traditional query repository


Before start, let’s see how a typical query repository looks like (assuming a fictitious Movies query repository):


public interface IMoviesQueryRepository
{
    Movie FindByID(Guid movieID);
    IQueryable<Movie> FindAll();
    IQueryable<Movie> FindByTitle(string title);
    IQueryable<Movie> FindByDirectors(string[] director);
    IQueryable<Movie> FindByActors(string[] actors);
    IQueryable<Movie> FindByReleaseDate(DateTime date);
}

There are several problems with an interface like this, I will talk about them in a minute.


The problem


Now let’s say that you start with a design like this and that you implement all these methods and use DI to inject the query repository wherever you want to use it. Now imagine that after awhile, you find out that you need to query your movies by Category so you just go and add the following method to your interface:


IQueryable<Movie> FindByCategory(string category);

You have to go and edit all your implementations of your IMoviesQueryRepository (including all testing mocks) in order to be able to compile again your solution, then you add implementation of this new method in all of them.


So far so good, after a couple of days you realize that now you need a couple of filters more, let’s say that now you need to query by a list of categories and by the average cost of making each movie, so again you add the following methods to your interface:


IQueryable<Movie> FindByCategories(string[] categories);
IQueryable<Movie> FindByAverageCost(decimal averageCost);

And you repeat all the implementation process again.


This process will continue during the whole development lifecycle. And there are several filters that might be needed during this time for example, querying by rating, by amount of tickets sold, etc. etc.


So a design like this is not flexible, is not testable and is hard to maintain


Let’s see several problems with this design:


  • It violates the open-closed pricniple (open for extension/closed to modification).


    The problem when you don’t follow this principle is the problem described before, you will have to go and edit all your implementations in order to add new functionality. You could potentially introduce new bugs to the application that would affect more than just one query (perhaps all of them?)

  • A design like this can lead to violate the YAGNI principle (You Ain’t Gonna Need It)


    Why? Simply because in order to avoid the problem described before, you could try to think about all the possible filters that you might need in the application. This will lead you to write tons of query filters that you won’t ever need

  • This design is not test friendly I just wrote the result of these queries as IQueryable objects on purpose to ilustrate the point that this design is not test friendly, and actually you won’t be able to test in isolation your queries if you expose in your public API IQueryable objects. The reason is that your query logic will actually be spread out in several places in your application.


Let’s see some approaches that won’t work


Anti Unit Testing approach


The simplest approach is to forget about unit testing (oO) and consume the IQueryable object directly in all the places where you will need it (usually the user interface)


I don’t have to tell you that I would never recommend something like this. But surprisingly in Stackoverflow I've seen a lot of code like this, and a lot of examples over the internet about MVC using this approach.


Naive approach


Well let’s say that you realize that unit test is so important in any application that you decide to go back to the original design and try to figure out how to fix it.


So a really simplistic approach would be to call for a small meeting with all your developers and tell them that even when you expose an IQueryable object in your query repository they should never add new queries on it.... in order to “guarantee” that the query is encapsulated and therefore testable in isolation.....


….Good luck with that!


So now hopefully you will want to see the solution already... and here it is


This is my solution to the problem


Encapsulating the query


The first thing to do to refactor the original design is to encapsulate the query in order to guarantee that the query will actually be unit-tested in isolation. I’m going to follow the same approach I used in my last post, I will create a new DTO object to wrap my query results.


This object would look something like:


public sealed class QueryResults
{
    public static QueryResults<TTarget> Of<TTarget>(IList<TTarget> results, int virtualRowsCount)
    {
        Condition.Requires(results).IsNotNull();
        Condition.Requires(virtualRowsCount).IsGreaterOrEqual(0);

        return new QueryResults<TTarget>(results, virtualRowsCount);
    }
}

public class QueryResults<TTarget>
{
    public QueryResults(IList<TTarget> results, int virtualRowsCount)
    {
        Condition.Requires(results).IsNotNull();
        Condition.Requires(virtualRowsCount).IsGreaterOrEqual(0);

        this.VirtualRowsCount = virtualRowsCount;
        this.Results = results;
    }

    public int VirtualRowsCount { get; protected set; }

    public IList<TTarget> Results { get; protected set; }
}

Since the query will be encapsulated you also need an object to pass paging and sorting parameters:


public sealed class PagingAndSortingInfo
{
    public PagingAndSortingInfo(
        int page = 1,
        int pageSize = 10,
        string orderByField = "",
        OrderDirection orderDirection = OrderDirection.Ascending)
    {
        Condition.Requires(page).IsGreaterOrEqual(1);
        Condition.Requires(pageSize).IsGreaterOrEqual(1); ;

        this.Page = page;
        this.PageSize = pageSize;
        this.OrderByField = orderByField;
        this.OrderDirection = orderDirection;
    }

    public int Page { get; private set; }

    public int PageSize { get; private set; }

    public string OrderByField { get; private set; }

    public OrderDirection OrderDirection { get; private set; }
}

public enum OrderDirection
{
    Ascending,
    Descending
}

So your query repository would look like:


public interface IMoviesQueryRepository
{
    Movie FindByID(Guid movieID);
    QueryResults<Movie> FindAll(PagingAndSortingInfo pagingAndSortingInfo = null);
    QueryResults<Movie> FindByTitle(string title, PagingAndSortingInfo pagingAndSortingInfo = null);
    QueryResults<Movie> FindByDirectors(string[] director, PagingAndSortingInfo pagingAndSortingInfo = null);
    QueryResults<Movie> FindByActors(string[] actors, PagingAndSortingInfo pagingAndSortingInfo = null);
    QueryResults<Movie> FindByReleaseDate(DateTime date, PagingAndSortingInfo pagingAndSortingInfo = null);
    QueryResults<Movie> FindByCategory(string category, PagingAndSortingInfo pagingAndSortingInfo = null);
    QueryResults<Movie> FindByCategories(string[] categories, PagingAndSortingInfo pagingAndSortingInfo = null);
    QueryResults<Movie> FindByAverageCost(decimal averageCost, PagingAndSortingInfo pagingAndSortingInfo = null);
}

Following this approach you will be able to unit test in isolation your queries, but you would still have the original problem.


Designing the Query Engine


OK, so now let’s design the query engine.


First of all I have a couple of interfaces that I’m using to mark my objects:


public interface IQuery
{
}

public interface IQueryID
{
}

  • The IQuery interface is the equivalent to the ICommand interface in the Commands branch in a CQRS architecture. This is used to mark value objects as query objects

  • The IQueryID interface is used to mark a value object as the query id. This will allows us to query by id using any type of id and even with composed id’s.


I created three interfaces in order to represent the queries in an application This is the core of the Query Engine:


public interface IQueryHandler<TQuery, TQueryResult> where TQuery : IQuery
{
    QueryResults<TQueryResult> HandleQuery(TQuery query, PagingAndSortingInfo pagingAndSortingInfo = null);
}

public interface IQueryHandler<TQueryResult>
{
    QueryResults<TQueryResult> HandleQuery(PagingAndSortingInfo pagingAndSortingInfo = null);
}

public interface IQueryByIDHandler<TQueryID, TQueryResult> where TQueryID : IQueryID
{
    TQueryResult HandleQuery(TQueryID queryID);
}

  • The IQueryHandler overload using the IQuery interface will be used to define explicitly the queries in your application

  • The IQueryHandler overload without the IQuery interface will be used to query all the records from the table

  • The IQueryByIDHandler will be used to query an entity/table/view by ID


This is a graphical representation:










Stay in touch, in the next post I will create base classes to encapsulate common behavior

1 comment:

  1. Hola juan tengo algunas Dudas con CQRS, quizas si tienes tiempo pudieras ayudarme, saludos.

    ReplyDelete

If you found this post useful please leave your comments, I will be happy to hear from you.