Journey to CQS – Part 2

This is second part of my post on CQS. In the first part I tried to explain how the Command part of CQS works. In this part I will try to explain the Query part. Lets see some key components that involved and their role in query part of CQS.

Query

Same as Command, Query is just a DTO that tell what you want to get from system. For example if you want to retrieve all orders by member then you need to create a Query dto (e.g GetOrdersByMemmberId) that contains the member id as one of its property. Sometimes you might have query that has no properties, Because query dto itself contains the information on its intention.

QueryDispatcher

This is mostly used from your controller. The role of dispatcher is to find the registered queryhandler in system that can handle the query and return the result your are after. This allow controller responsibility very specific, get data and pass that to view. Controller don’t have any direct reference to any query handler. Querydispatcher find the appropriate handler for the controller.

QueryHandler

You need to define a query handler for your query and query dispatcher will call this class to process the command. Generally here you will define all you code to process a query and return result. For example, call repository to load the orders by member id.

CacheStore

This is not part of CQS directly. But this allow to add cache for any queryhandler in a loosely coupled fashion. When we need to provide cache for any query we can just write a cachestore for that query and register in IOC. If item exists in cache the queryhandler never calls and data returned from cachestore.

QueryResultFilter

This class generally used to load some related data or modify the result before returning to the consumer. You can add more than 1 filter for a query. For example: For each product we need to display number of viewcount. Rather than loading that viewcount from database using join/group inside repository and queryhandler we can just add a result filter. That filter can use redis cache to load total viewcount for each product and fallback to database.

Show me some code

Lets say we have following requirements:

As a user I should able to get books with paging

  • The books should return with paging
  • Each books should have total viewcount
  • Each books should have rating

As you see the requirements here is to read data from source. So we have define a Query object that should have express what the query for. A good class name for this query can be “GetBooks” with two properties “PageIndex” and “PageSize”.

Lets define the query class to get books and response dto that we will return to user:


public class GetBooks : IQuery
{
	public int PageIndex { get; set; }
	public int PageSize { get; set; }
}

public class BookListItem
{
	public long Id { get; set; }
	public string Title { get; set; }
	public string Summary { get; set; }
	public decimal Price { get; set; }
	public long ViewCount { get; set; }
	public int Rating { get; set; }
}

In the first part of this post we created a BooksController class. Lets add this action in that controller the handle get books request.


[Route(Name = "GetBooks")]
public async Task<IHttpActionResult> Get([FromUri] GetBooks query)
{
    var result = await _queryDispatcher.DispatchAsync<GetBooks, PagedResponse<BookListItem>>(query);

    return Ok(result);
}

But we didn’t create and handler for this query. So next step will be create a queryhandler for this query and register that handler in your IOC.


public class GetBooksQueryHandler : IAsyncQueryHandler<GetBooks, PagedResponse<BookListItem>>
{
	private readonly IBooksRepository _booksRepository;
	private readonly IMapper _mapper;

	public GetBooksQueryHandler(IBooksRepository booksRepository, IMapper mapper)
	{
		_booksRepository = booksRepository;
		_mapper = mapper;
	}

	public async Task<PagedResponse<BookListItem>> HandleAsync(GetBooks query)
	{
		if(query.PageIndex == 0) query.PageIndex = 1;
		if(query.PageSize == 0) query.PageSize = 15;

		var booksPagedResponse = await _booksRepository.GetBooksAsync(query);

		return new PagedResponse<BookListItem>
		{
			TotalItems = booksPagedResponse.TotalItems,
			TotalPages = booksPagedResponse.TotalPages,
			Items = booksPagedResponse.Items.NullSafe().Select(x => _mapper.Map<Book,BookListItem>(x))
		};
	}
}

// IOC Binding
Bind<IAsyncQueryHandler<GetBooks, PagedResponse<BookListItem>>>().To<GetBooksQueryHandler>(); 

Queryhandler here calls booksrepository class to get books based on query. But repository return paged response of Book domain object. Using mapper it converts the domain object to BookListItem and return as pagedresponse of BookListItem. Now our controller endpoint should return books with paging details like totalrecords and totalpages.

But if you check the result the items does not contains any viewcount which is our next requirement. We will load this data for each items from redis cache inside QueryResultFilter. Lets see the code for QueryResultFilter:


public class LoadViewCountFilter : IAsyncQueryResultFilter<GetBooks,PagedResponse<BookListItem>>
{
	private readonly IRedisClient _redisClient;

	public LoadViewCountFilter(IRedisClient redisClient)
	{
		_redisClient = redisClient;
	}

	public async Task FilterAsync(GetBooks query, PagedResponse<BookListItem> result)
	{
		var booksWithViewCount = await _redisClient.GetViewCountAsync<BookIdWithViewCount>(result.Items.NullSafe().Select(x => x.Id));

		var items = result.Items.NullSafe().Select(x => {
				x.ViewCount = booksWithViewCount[x.Id];
				return x;
			});

		result.Items = items;
	}
}

public class LoadRatingFilter : IAsyncQueryResultFilter<GetBooks,PagedResponse<BookListItem>>
{
	private readonly IRedisClient _redisClient;

	public LoadRatingFilter(IRedisClient redisClient)
	{
		_redisClient = redisClient;
	}

	public async Task FilterAsync(GetBooks query, PagedResponse<BookListItem> result)
	{
		var booksWithRating = await _redisClient.GeRatingAsync<BookIdWithRating>(result.Items.NullSafe().Select(x => x.Id));

		var items = result.Items.NullSafe().Select(x => {
				x.Rating = booksWithRating[x.Id];
				return x;
			});

		result.Items = items;
	}
}

// IOC binding
Bind<IAsyncQueryResultFilter<GetBooks,PagedResponse<BookListItem>>>().To<LoadViewCountFilter>();
Bind<IAsyncQueryResultFilter<GetBooks,PagedResponse<BookListItem>>>().To<LoadRatingFilter>();

Now the getbooks endpoint will return rating and viewcount for each items from cache. But we didn’t try to load all these from queryhandler. Filters allow to alter the result without too much code in queryhandler. The responsibility also distributed to different filters also. Imagine now the stackholder wanted to provide discount on price for premium members. You can then write another filter that check for member status and apply discount on price. You can achieve new feature without changing any existing code but by adding a new result filter.

Now after deployment you find that under heavy traffic the system not performing well. So you decide to cache the pagedresponse in memory so that under heavy load you can provide result without querying database. Lets create a CacheStore for this and bind that cachestore in IOC.


public class GetBooksCacheStore : IAsyncCacheStore<GetBooks,PagedResponse<BookListItem>>
{
    private const string Key = "Books:GetBooks:{0}:{1}";
    private readonly ICacheProvider _cacheProvider;

    public GetBooksCacheStore(ICacheProvider cacheProvider)
    {
        _cacheProvider = cacheProvider;
    }

    public async Task<PagedResponse<BookListItem>> GetAsync(GetBooks query, Func<Task<PagedResponse<BookListItem>>> fetch)
    {
        var key = Key.FormatWith(Key, query.PageIndex, query.PageSize);

        return await _cacheProvider.GetAsync<PagedResponse<BookListItem>>(key, TimeSpan.FromMinutes(5), fetch);
    }
}

//IOC bind

Bind<IAsyncCacheStore<GetBooks,PagedResponse<BookListItem>>>().To<GetBooksCacheStore>();

Thats it we applied caching for GetBooks functionality.

I am trying to show how the CQS helps the development of well structured, loosely coupled and flexible system which is easy to change and testable. You can read the first part here. That’s all for today. Happy coding 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s