Introduction
This article shows a way of making a RESTful interface to implement the query side of a Command Query Responsibility Segregation architecture. It is implemented in VB.NET, but there is nothing in it preventing a very quick conversion to C# if that is your language of choice.
Background
At its simplest, CQRS is an architecture pattern that separates commands (doing) from queries (asking). What this means in practice is that the command and query side do not need to share a common model and this vertical shearing layer is often matched with a vertical shearing layer between the definition (of a command or query) and the implementation (in a command handler or a query handler).
In my experience, CQRS is very useful when you are developing an application with a distributed team to a very short timescale as it reduces the risk of "model contention" which can occur if everyone is updating the model at the same time. It is also useful for financial scenarios where having an audit trail of all the changes that have occurred in an application is required.
The Challenges
There are a couple of challenges I wanted to address on the query side that affected how the architecture went together:
Minimise the Payload
Passing state information back and forth and indeed any system that maintains state is going to result in an impediment to scaling. In practice, this means that every query must have all the information required to fulfil the request in the query definition alone.
Independent Development
A shearing layer is needed between the front end (in this case, the web service) and the actual implementation of the query logic. This allows the how part to change without impacting the what part.
Query Definitions and Query Handlers
The architecture is therefore split into two components - the (rigid) query definition which states what is being requested and the related query handler which performs the work of servicing the query.
Query Definition
All query definitions inherit from an interface - IQueryDefinition
- which uniquely identifies the query type, specific instance and any parameters it will require.
Public Interface IQueryDefinition
ReadOnly Property InstanceIdentifier As Guid
ReadOnly Property QueryName As String
Sub AddParameter(ByVal name As String, ByVal index As Integer, ByVal value As Object)
ReadOnly Property Parameters As IEnumerable(Of QueryParameter)
End Interface
This interface is extended so it also defines what the expected return type for the query is to be:
Public Interface IQueryDefinition(Of TResult)
Inherits IQueryDefinition
End Interface
Depending on your business scenario, you might also like to add properties that describe the query context - when it was submitted, the address and credentials used to submit it and any priority to be applied to it. This can be done by adding a wrapper interface around the query definition so that the context is still kept distinct from the parameters required by the query itself.
Query Handler
The query handler takes the query definition and returns the required TResult
. There is a one to one mapping between definition classes and handler classes, which could be resolved using Unity, for example.
Public Interface IQueryHandler(Of Out TResult)
Function Handle(ByVal query As IQueryDefinition) As TResult
End Interface
The job of the query handler is to validate the correctness of the query context and then to use the query parameters to perform the data retrieval from the underlying data source and finally to coerce it into the return type expected by the query definition.
Routing the URI to the Appropriate Query Definition
All the queries come in on the same root URL so the routing to the correct one is done by the URI template (which, therefore, has to uniquely identify the query and its parameters).
This is done by having each query definition having a static
function that returns its URI template and using these in the WCF service definition:
<OperationContract()>
<WebGet(ResponseFormat:=WebMessageFormat.Json,
RequestFormat:=WebMessageFormat.Json,
BodyStyle:=WebMessageBodyStyle.Wrapped,
UriTemplate:=GetBazaarsForClientQueryDefinition.UriTemplate)>
Function GetBazaarsForClient(ByVal clientId As String, ByVal query As String) _
As IReadOnlyList(Of BazaarSummary)
History
- 14th April, 2014: Initial version