CQRS - Automatically Visualize and Document Your Solution
Solutions based on the Command-Query Responsibility Segregation (CQRS) principles can get a little bit overwhelming, as more handlers and events are added.
We can reduce some perceptive complexity by auto-generating documentation and visual graphs to represent relations between the commands, events and their consumers.
The codebase already has all the information about your domain, it just needs a little bit of help to be visualized and explored in a comprehensive manner.
Let's start with the end. That's the graph that I quickly generated for my current project to get a big picture of what's currently going on.

That's a big picture already. Let's zoom in:

Obviously this picture represents current state of the things and will change multiple times before this day ends. Generating it manually would not be efficient. So let's see how it could be auto-generated on a repeated basis.
We assume, that following interfaces are defined and used in the solution:
public interface IMessage {}
public interface IConsume {}
public interface IConsume<TMessage>
where TMessage : IMessage
{
void Consume(TMessage message);
}
Additionally, this approach assumes, that the simplified CQRS scenario is leveraged. In this scenario we don't get deep into the DDD:
- message handlers contain the domain logic;
- message handlers are responsible for selecting, which events to publish;
- event enrichment happens in the handlers as well.
All messages (events and commands) and consumers (command and event handlers) derive from these interfaces. This simplifies auto-wiring and auto-documentation processes.
We start by getting into Visio 2010 interop (or whatever version you have installed):
var app = new Microsoft.Office.Interop.Visio.Application();
var doc = app.Documents.Add("");
var page = doc.Pages[1];
Then we reflect upon and draw shapes for all the messages in our system. In my current project all messages derive from an empty IMessage interface so are easy to identify:
var exportedTypes = typeof(IMessage).Assembly.GetExportedTypes();
var messages = exportedTypes
.Where(t => typeof(IMessage).IsAssignableFrom(t))
.Where(t => !t.IsAbstract)
.ToArray();
for (int i = 0; i < messages.Length; i++)
{
var y1 = i*0.75;
var x1 = 0;
var shape = page.DrawRectangle(x1, y1, x1+3, y1 + 0.5);
shape.Name = messages[i].FullName;
shape.Text = messages[i].Name;
shape.Cells["Rounding"].Formula = "2 mm";
shape.Cells["FillForegnd"].FormulaU =
"THEMEGUARD(MSOTINT(THEME(\"FillColor\"),40))";
}
Same could be done with the message consumers:
var consumers = exportedTypes
.Where(t => typeof(IConsume).IsAssignableFrom(t))
.Where(t => !t.IsAbstract)
.ToArray();
for (int i = 0; i < consumers.Length; i++)
{
var y1 = i * 0.75;
var x1 = 4;
var shape = page.DrawRectangle(x1, y1, x1 + 3, y1 + 0.5);
shape.Text = consumers[i].Name;
shape.Name = consumers[i].FullName;
shape.Cells["LineWeight"].Formula = "2 pt.";
shape.Cells["FillForegnd"].FormulaU =
"=THEMEGUARD(MSOTINT(THEME(\"AccentColor2\"),60))";
}
Then we could add connections from the messages to their handlers:
foreach (var consumer in MessageDirectory.Consumers)
{
var messageType = consumer.Key;
foreach (var consumerType in consumer)
{
var messageShape = page.Shapes[messageType.FullName];
var consumerShape = page.Shapes[consumerType.FullName];
messageShape.AutoConnect(consumerShape,
VisAutoConnectDir.visAutoConnectDirRight);
}
// we'll add something here later
}
Where MessageDirectory.Consumers is populated by reflection:
Consumers = LookupMessageConsumers()
.ToLookup(p => p.Value, p => p.Key);
static IEnumerable<Pair<Type, Type>> LookupEventConsumers()
{
var consumers = Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => !t.IsAbstract)
.Where(t => typeof (IConsume).IsAssignableFrom(t)).ToArray();
foreach (var c in consumers)
{
var interfaces = c
.GetInterfaces()
.Where(i => i.Name.StartsWith("IConsume"))
.Where(i => i.IsGenericType);
foreach (var consumerInterface in interfaces)
{
yield return Tuple.From(c, consumerInterface.GetGenericArguments()[0]);
}
}
We could go further from here and draw connections from the consumers directly to the messages that were created within these consumers. We do this by some IL parsing. I'll be using Lokad.Quality (embedding Mono.Cecil and some helpers), to achieve this.
We start, by reading the codebase:
var c = new Codebase(typeof(IMessage).Assembly.Location);
var mt = c.Find<IMessage>();
Then, we perform the actual IL parsing and build connections. The code snippet below goes to the line marked with "// we'll add something here later" from the snippet above.
var calls = c.Find(consumerType).Value
.GetAllMethods(c)
.SelectMany(m => m.GetInstructions())
.Where(i => i.OpCode == OpCodes.Newobj)
.Select(i => ((MemberReference) i.Operand).DeclaringType)
.Select(t => c.Find(t))
.SelectValues()
.Where(t => t.GetInheritance(c).Contains(mt));
foreach (var typeDefinition in calls)
{
var publishedShape = page.Shapes[typeDefinition.FullName];
consumerShape.AutoConnect(publishedShape,
VisAutoConnectDir.visAutoConnectDirRight);
}
The final touch would be to add arrow heads to all the connectors and perform auto-layout:
foreach (Shape shape in page.Shapes)
{
if (shape.Name.Contains("connector"))
{
shape.Cells["EndArrow"].Formula = "4";
}
}
page.Layout();
There is one manual step that I like to perform: "Design | Re-layout | Hierarchy (Top to bottom)". This seems to produce the best layout for the DDD message relationships.
Keep in mind, that the powers of development intelligence do not necessarily end here. For example, in Lokad development projects we do things like:
- Putting read-me files (marked as resources) into the every feature-specific folder (their presence is enforced by the unit test).
- Generating documentation in markdown formatting (familiar to everybody by StackOverflow) by enumerating all features and commands in a nice way, while adding these read-me files in the appropriate spots.
- Using a little bit more of reflection to print out whatever performance counters, auto-generated issues and extra features, the specific handler does use.
- Using a little bit of meta-data (i.e.: Autofac/MEF) to display some pieces of documentation within the web applications in a SEO-friendly way.
All the procedures above happen only once. I don't really bother maintaining documentation, since the code is the best documentation. Yet, following some conventions (like using the IMessage and IConsume interfaces) significantly simplifies complexity of building various graphs, charts or wiki articles.
When there is a release, this generation routine could be run once, with the output sent to markdown-to-html converter. The resulting HTML is merely copied to the project wiki.
There is an interesting side-effect of developing with auto-documentation in mind. Similar to the unit-testing, this forces the resulting code to be more explicit and understandable (and simpler in the long run), even before the documentation is actually generated.
Although this is a snippet, I'll probably eventually extend it towards the enterprise monitoring tool, in order to add data graph statistics and visualizations (for example, using data graphs from Visio 2010 to display actual performance numbers of messages and handlers within the same graph). Managing cloud enterprise buses does require a big picture.
Another potential improvement is linked to the fact (highlighted by Greg Young), that the simplified CQRS scenario is leveraged in this approach. It does not have DDD features. As we move towards the proper domain objects, IL parsing would need to become smarter and inspect method call chains invoked from the handler.
In the next article we will expand on the topic and show how to use auto-layout algorithms to create nice dependency graphs in the complex architectural scenarios. Visualizing IoC Container dependencies would also be mentioned. Read more.
Related posts:
- Enterprise Monitoring Dashboard - iPad for Enterprise Developer
- Maintenance and Monitoring Infrastructure for the Enterprise Software Systems
If you liked this article, you can stay updated by subscribing to the RSS feed. You can also keep an eye on the xLim 4 research and development project, where this article belongs.
Wednesday, April 14, 2010 at 19:17
Reader Comments (2)
This seems to be dependent on creating events in command handlers then pushing the event into the domain to validate. This can work well in some isolated cases (the simplest) but generally should be avoided. The reason for this is that many times which event gets created is dependent on the state of the domain object itself.
Consider the example of modeling a security in the stock market. The command is "Place Order to Buy 1000 Shares at $50". There is serious domain logic to figure out which events should be produced. it will be some combinations of trades and/or an order being placed. This case is more common than many believe. The only object that knows how to actually generate the events is the domain object, building the events in the command handler would be a terrible violation of SRP.
Along with this case is the enrichment that needs to happen. You build up a PurchaseCompletedEvent then pass it into a domain object ... What about the sales tax that is calculated as part of this process? What if the domain is integrating synchronously with payments and needs to append this information? Another example might be complex logic assigning priorities based on the customer etc. There are a host of reasons why you may need to add calculations produced in the domain
@Greg,
yes, you are correct, I'm using the simplified CQRS approach for this scenario. That's basically CQRS without the DDD elements.
Updated the article.