From the beginning of my current project we have been working under some horrible constraints, many imposed by legacy systems, many by late decisions that would have speeded things immensely if made earlier, and many imposed by decisions that are outside of my control.
This lead us early on to make decisions on architecture that eliminated any possibility of using DDD heavily, and as I mentioned in my original post about this project it is also essentially a glorified CRUD system - we read data from databases, we modify it a bit, and we put it back again.
What We Are Doing – Command Query Separation
The first architectural choice that I made, and in hindsight got accepted with relative ease, was to employ CQS to simplify the system.
Earlier projects had suffered quite badly from being abstractions of legacy systems with new functionality bolted on. As
far as possible I wanted to eliminate this problem from our project, as I was all too aware that this was an easy trap for the team to fall into, and would burn a lot of development time very fast.
To make the system simpler I chose to separate our query mechanisms from the "domain" type stuff, our commands and data writing/updating code.
CQS is a pretty simple concept. One path through your system is responsible for querying of anything much more than "Get By ID" type calls. This side of our system is responsible for reading data from legacy systems, reading data from legacy web services, and for reading data from our application database.
Querying
The Query side deals largely in DataTables, DataSets and a very very limited number of DTOs. This data is largely and essentially used for display purposes, it is the contents of search results, the results of postcode lookups, the information to populate dropdown lists and tables.
The Query side of CQS does not need strongly typed entities, nor does it require strongly typed DTOs - as it is largely ad-hoc data maintaining these entities and DTOs would consume a disproportionate amount of development time for something a DataTable can deal with more than adequately.
Our Query side actually uses the Query Object pattern, most of which encapsulate a call to an Oracle DB, some of which wrap simple NHibernate criteria and a few of which sit around web services. These are fronted by a WCF facade, which allows us to put these queries out of process, and to cache them aggressively should be need.
What we do not have in the system anywhere, and especially in the Query side, is any big entity model – we have two very small entity models (5 entities in total) used for very specific functions within our application.
Command
The Command side of the CQS equation is a little different. Firstly, it deals with anything that is essentially a request to "go do something". In our case these are commands like "SaveDraft" and "CancelPolicy"
The Command side can write data to the databases, the Query side is not allowed to.
If we were doing DDD, this is where our Domain would sit - and in fact we have been referring to this as "the domain" for most of this project. But, we aren’t using DDD and we don't really have a Domain Model. It is however where most stuff we could describe as "logic" lives. This is primarily a bunch of application services, most of which operate in response to messages fired over MSMQ using NServiceBus.
This side of the architecture has a number of listeners that are responsible for pulling messages from various MSMQs and for dealing with the specific requests, for example pushing data back to our legacy systems, for logging, and eventually for notifications.
Benefits
Early on, the use of CQS paid for itself easily. By removing the complications of maintaining read and write models together, we quickly managed to get working prototype up and running, with no need for a "domain" or entity model up front.
As the project has progressed, this decision has helped simplify decisions that otherwise would have involved structural or fundamental changes to the architecture – but more importantly, it has helped all of the developers think in terms of a command driven system, rather than viewing the system as CRUD over a database.
Posted
06-22-2009 5:32 PM
by
Jak Charlton