Brownfield CQRS part 3 – Queries, Parameters and Results

Spread the love

In the previous two posts, I showed some simple patterns for commands and command handlers. Now let’s talk about the other half of the story: queries!

  • Brownfield CQRS part 1 – Commands
  • Brownfield CQRS part 2 – Command Handlers
  • Brownfield CQRS part 3 – Queries, Parameters and Results
  • Brownfield CQRS part 4 – Command Dispatcher

On our WCF service, each query method:

  • Returns one or more QueryResult objects — a DTO created exclusively for this query.
  • Takes a QueryParameter argument — if required, a DTO containing search criteria, paging options etc.

For example, to query for bookings:

[ServiceContract]public interface IBookingService{    [OperationContract]    IEnumerable<BookingQueryResult> SearchBookings(        BookingSearchParameters parameters);}

Query Parameters

Queries take simple DTO parameter objects just like commands. They carry both search criteria (what to look for) and things like paging options (how to return results). They can also define defaults. For example:

[DataContract]public class BookingSearchParameters{    public BookingSearchParameters()    {        // default values        NumberOfResultsPerPage = 10;        Page = 1;    }    [DataMember]    public Tuple<DateTime, DateTime> Period { get; set; }    [DataMember]    public int NumberOfResultsPerPage { get; set; }    [DataMember]    public int PageNumber { get; set; }}

Query Object

Queries are then executed by a query object — an application service that queries your ORM, reporting store, or domain + automapper (if you’re still using a single model internally for commands and queries).

public interface IQuery<TParameters, TResult>{    public TResult Execute(TParameters parameters);}

Query Results

Queries can return a single result (e.g. to look up the details of a specify item), or a sequence (searching):

public class BookingSearchQuery :     IQuery<BookingSearchParameters, IEnumerable<BookingSearchResult>>{    public IEnumerable<BookingSearchResult> Execute(        BookingSearchParameters parameters)    {        ...    }}

Query results are simple DTOs that provide all the information the client needs.

[DataContract]public class BookingSearchResult{    [DataMember]    public string PartyName { get; set; }    [DataMember]    public int PartySize { get; set; }    [DataMember]    public DateTime TimeAndDay { get; set; }    [DataMember]    public string SpecialRequests { get; set; }}

Query Results should be able to be rendered directly on the UI, in one query. If the they require further mapping, or multiple calls (e.g. to get different aspects of an object) before you can use it on a view model, then they are most likely:

  • Too granular — query results should be big flattened/denormalized objects which contain everything you need in one hit.
  • Based on the wrong model (the domain or persistence model) — they should be based on the UI’s needs, and present a screenful of information per call.

As with commands, having a one-to-one mapping between query objects and queries makes it easy to add/remove functionality to a system.

Can’t I use the same object for sending commands and returning results?

That BookingSearchResult looks more or less identical to BookTableCommand we sent before — it has all the same properties. That doesn’t seem very DRY! Can’t I just create a generic DTO and use that in both cases?

Using the same class for commands and queries is called CRUD, and it leads to exactly the sort of situations we are trying to avoid — where commands needs to use different representations of objects than when querying, but can’t because they are tightly coupled to share the same object. As I said in part 1, commands are driven by business transactions, but queries are driven by UX needs, and often include projections, flattening and aggregation — more than just 1:1 mapping.