Validating Architecture through LINQ Query | Patrick Smacchia
These days we are restructuring the NDepend code base to make it more suited to welcome future features implementation. Here is below the new architecture of the NDepend.UI assembly, made of around 50.000 lines of code, shown through a Dependency Structure Matrix:
As always, the dependency matrix naturally exhibits architectural patterns. Keep in mind that a blue cell means: the namespace in column is using the namespace in row. The number on a blue cell represents the number of members used (see the option in the matrix menu: Weight on Cells set to Direct: # members). This cell number is then a measure of the coupling between the two namespaces.
Here are architecture patterns emerging from this dependency matrix:
- There is one Top namespace, that is using all namespaces (blue column #0)
- There is one Base namespace, that is used by all namespaces (blue row #17)
- The Panels namespaces don’t use each others (empty big red square, rows/columns [1-12])
- The Shared namespaces don’t use each others (empty small red square, rows/columns [13-16])
- The blue cells in the rectangle rows [13,16] x columns [1,12], represents the usage of shared features by the panels. For example, on row #15 we can see that the DependencyContextMenu feature is used by the Top, Graph and Matrix impl.
I found this NDepend.UI architecture pretty neat. Whether we will add a new panel or a new shared feature in the future, it will be inserted naturally in this structure. And this architecture is simple enough to get validated at a glance. But at a glance is not enough. What about defining custom rules that take care of validating this architecture and inform us automatically of any violation?
Each of this architecture pattern could be actually validated through a Code Query LINQ rule. Let’s go through 5 steps to write the rule Panels shouldn’t use each others through a LINQ query.
Step 1: First, let’s match all panels namespaces. Here we are using the extension method WithNameWildcardMatch() to match the panels namespaces by name. We can see that some panels (Matrix, QueryEdit, Search…) are made of several sub-namespaces. The rule must be smart enough to both:
- let sub-namespaces of a panel use each others,
- forbid sub-namespaces of different panels use each others:
Step 2: Now let’s use the method IUser.IsUsing() to list the couples of [namespace user, namespace used]. Notice that to improve the query performance:
- we are restraining the set of namespaces user only to the panels namespaces that are indeed using another panel namespace (with the extension method UsingAny() )
- we are restraining the set of namespaces used only to the panels namespaces that are indeed used by another panel namespace (with the extension method UsedByAny() )
And indeed the query runs fast: 5 milli-seconds on a more than 130.000 lines of code code base! There are 45 couples of [namespace user, namespace used] listed:
Step 3: Now, we need to validate for each couple of [namespace user, namespace used], that both user/used namespaces belong to the same panel. For that we have to work on the namespace name string. Let’s first get rid of the prefix “NDepend.UI.Panels.” and obtain for each namespace what we call its sub-name:
Step 4: Let’s get the panel name from the namespace sub-name. For that we are using the ternary operator: if the namespace sub-name doens’t contain any dot, it is already the panel name. Else, the panel name is the sub-string until the first dot met in the sub-name:
Step 5: Et voila! We now just have to make sure that for each couple of [namespace user, namespace used] they have the same panel name! This is as simple as adding the where clause: where nUserPanel != nUsedPanel.
We then add the prefix warnif count > 0 to transform our code query into a code rule. Plus, we set the name of the rule to Panels shouldn’t use each others. And the rule is ready to be saved, and to be run continuously in Visual Studio and/or on the build machine.
Note that this rule gets compiled and executed in only 7 milli-seconds (see 7 ms on the screenshot). This means that a typical machine can run in one second more than 140 of such complex rules against a large code base!
And also, when new panels will be developed, this rule won’t have to be updated. It’ll just work!
Could it be easier or faster to validate the architecture of a big lump of code?
Btw, just a quick side remark: The fact that panels implementations are not using each other doesn’t mean that panel shouldn’t interact with each other. They actually do interact a lot with each others. But they do interact through using a mediator implementation that lives in the Base namespace. This mediator then calls the Top implementation (through inversion of control) that takes care of dispatching any interaction to the right panel.