Microservices Re-architecture Part 5: Practical CQRS - Queries

Tags: microservices

CQRS is an architectural pattern that is not necessarily relevant to microservices, but it can be a powerful tool in your architecture toolbox.


Cross Cutting Concerns

The main reason we implemented CQRS in our microservices was to handle cross cutting concerns. Some of these are:

  • Error handling
  • Audit logging
  • Encryption/Decryption
  • Transactions
  • Event sourcing


Being able to handle these tasks in a comprehensive and consolidated manner is the biggest advantage of CQRS in my book.

Fat Interfaces

In prior architectures, our business layer consisted of a few very wide interfaces that had methods for doing a megaton of things for a single business domain. Some example methods would be:

  • IEnumerable<Offer> GetOffers();
  • IEnumerable<Offer> GetOffersByVerticalID(int verticalID);
  • Offer GetOfferByID(int offerID);
  • bool CreateOffer(ref Offer offer);
  • bool SaveOffer(Offer offer);
  • bool DeleteOffer(int offerID);

It wouldn’t be out of the ordinary for an interface to have 50 method signatures. That’s a chubby interface.

CQRS Is...

Simply put, CQRS is Command Query Responsibility Segregation. Queries get something. Commands do something. Each command or query is a class that does one thing. Sticking with the business object mentioned above, a query might get an offer by its ID. A command might create an offer.

Why CQRS

Commands and queries are fundamentally different. Commands change data; queries return data. The power of separating them is that we can isolate code that we want to execute for every command or query. Let's say we want to raise an event whenever a business object is changed or created in order to update a cache. We can do that uniformly for all commands with one class. Or perhaps we want to capture error info for every query; we can do that in one class too! Power!!

Another benefit is that we have simple classes that are easy to unit test.  They adhere to SOLID priciples and are DI friendly.

Constructing Queries

In our CQRS implementation we call a class that is the input to a data request a query and the class that executes the data request, the query handler. Each query implements a common interface, IQuery, and each query handler implements a common interface, IQueryHandler. Here's what they might look like:

public interface IQuery<TResult>
{}

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
Task<TResult> HandleAsync(TQuery query);
}

IQuery is simply an empty interface to define a type that an IQueryHandler can accept as an input. The actual input is a property of the implementation of IQuery and can be whatever you want. IQueryHandler defines one method, HandleAsync, that executes the query. Normally your QueryHandler will take a repository as an input. An example implementation might be:



public class GetOffersQueryHandler: QueryHandlerBase<GetOffersQuery, IList> 
{ 

  public GetOffersQueryHandler(MongoRepository mongoRepository) 
  { 

    public async Task<IList> HandleAsync(GetOffers query) 
    { 
      if (query == null) throw new ArgumentNullException(query.GetType().Name); 
      var offers = managementMongoRepository.GetQueryable().ToList(); 
      return offers; 
    } 
  
  } 
}


This is a simple query handler that returns all the offers. You could have more complex query handlers that join a few objects and return a more complex object.

So what you're going to end up with now is a whole bunch of tiny classes, rather than one monolithic class implementing a fat interface. That's much more in line with the Single Responsibility Principle and Interface Segregation Principle.

Now you can take these nifty query handlers and inject them into your MVC controllers as needed. A DI container can handle all that for you.

Cross Cutting Benefits

The cross cutting benefits really shine on the command side of this architecture but on the query side I implemented a pretty neat error handling cross cutting concern. But first let's talk about the mechanics of implementing the cross cutting code.

Decorators

Cross cutting code can now be implemented via decorators. A decorator is basically just a wrapper around a query handler that adds functionality. It's one query handler wrapping another.
Here is a decorator I implemented to capture a query information in the event of an exceptoin.


public class ErrorLoggerQueryHandlerDecorator<TQuery, TResult> : IQueryHandler<TQuery, TResult> where TQuery : IQuery 
{ 
   private readonly IQueryHandler<TQuery, TResult> decorated; 
   public ErrorLoggerQueryHandlerDecorator(IQueryHandler<TQuery, TResult> decorated) 
   { 
     this.decorated = decorated; 
   } 
 
   public Task Handle(TQuery query) 
   { 
     try 
     { 
         return this.decorated.Handle(query); 
     } 
     catch (Exception ex) 
     { 
        ex.Data.Add("query", Newtonsoft.Json.JsonConvert.SerializeObject(query)); 
        throw; 
     } 
   } 
} 

What this decorator does in the event of an error is catch the error, serialize the query input, add it to the exception's data collection, then rethrow the error. Now when we log the error upstream, we have the input logged for debugging.

Now we just need to wrap this decorator around every query handler and we have this data captured across the board. Wrapping the decorators can be done in a dependency injection container with one line of code. Power!

More on DI and the CQRS command side coming soon.

A really great blog post on this pattern is on .Net Junkie

1 Comment

  • Khoi said

    Beautiful!

    Recently I also refactored the fat interfaces/application services into Commands and Queries.

    This also turns out a lot of benefits in case you have multiple implementations for a single fat interface.

    In your design, I wonder why we need to define result type we expect for an input. Why we not leave it in the handler?

    I mean rather than IQuery<TResult> we can simply put it as a IQuery. It's because in handler, we already define both request and response.

    public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>

    Second thing, in a handler, accepting a repository via constructor is being done by a DI container I guess. But how do you use DI in the decorator as you said? I'm curious that part.

    Last thing, writing comments here is not easy!!!

Add a Comment