This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

The Road to Quino 2.0: Maintaining architecture with NDepend (part II)

Description

In the <a href="{app}view_article.php?id=3058">previous article</a>, I explained how we were using NDepend to clean up dependencies and the architecture of our Quino framework. You have to start somewhere, so I started with the two base assemblies: Quino and Encodo. Encodo only has dependencies on standard .NET assemblies, so let's start with that one. The first step in cleaning up the Encodo assembly is to remove dependencies on the Tools namespace. There seems to be some confusion as to what belongs in the Core namespace versus what belongs in the Tools namespace. There are too many low-level classes and helpers in the Tools namespace. Just as a few examples, I moved the following classes from Tools to Core: <ul> BitTools ByteTools StringTools EnumerableTools </ul> The names kind of speak for themselves: these classes clearly belong in a core component and not in a general collection of tools. Now, how did I decide which elements to move to core? NDepend helped me visualize which classes are interdependent. <h>Direct Dependencies</h> <img attachment="enumerable_tools_depends_on_string_tools.png" align="left" caption="Enumerable tools depends on String Tools">We see that <c>EnumerableTools</c> depends on <c>StringTools</c>. I'd just moved <c>EnumerableTools</c> to <c>Encodo.Core</c> to reduce dependence on <c>Encodo.Tools</c>. However, since <c>StringTools</c> is still in the <c>Tools</c> namespace, the dependency remains. This is how examining dependencies really helps clarify a design: it's now totally obvious that something as low-level as <c>StringTools</c> belongs in the <c>Encodo.Core</c> namespace and not in the <c>Encodo.Tools</c> namespace, which has everything but the kitchen sink in it. <img attachment="messagetools_and_encodo.tools.png" align="left" caption="MessageTools and Encodo.Tools dependencies">Another example in the same vein is shown to the left, where we examine the dependencies of <c>MessageTools</c> on <c>Encodo.Tools</c>. The diagram explains that the colors correspond to the two dependency directions.<fn> We would like the <c>Encodo.Messages</c> namespace to be independent of the <c>Encodo.Tools</c> namespace, so we have to consider either (A) removing the references to <c>ExceptionTools</c> and <c>OperatingSystemTools</c> from <c>MessageTools</c> or (B) moving those two dependencies to the <c>Encodo.Core</c> namespace. Choice (A) is unlikely while choice (B) beckons with the same logic as the example above: it's now obvious that tools like <c>ExceptionTools</c> and <c>OperatingSystemTools</c> belong in <c>Encodo.Core</c> rather than the kitchen-sink namespace. <h>Indirect Dependencies</h> Once you're done cleaning up your direct dependencies, you still can't just sit back on your laurels. Now, you're ready to get started looking at <i>indirect</i> dependencies. These are dependencies that involve more than just two namespaces that use each other directly. NDepend displays these as red bounding blocks. The documentation indicates that these are probably good component boundaries, assuming that the dependencies are architecturally valid. NDepend can only show you information about your code but can't actually make the decisions for you. As we saw above, if you have what appear to be strange or unwanted dependencies, you have to decide how to fix them. In the cases above, it was obvious that certain code was just in the wrong namespace. In other cases, it may simply be a few bits of code are defined at too low a level. <h>Improper use of namespaces</h> For example, our standard practice for components is to put high-level concepts for the component at the <c>Encodo.<componentname></c> namespace. Then we would use those elements from sub-namespaces, like <c>Encodo.<componentname>.Utils</c>. However, we also ended up placing types that then used that sub-namespace in the upper-level namespace, like <c>ComponentNameTools.SetUpEnvironment()</c> or something like that. The call to <c>SetUpEnvironment()</c> <i>references</i> the <c>Utils</c> namespace which, in turn, <c>references</c> the root namespace. This is a direct dependency, but if another namespace comes between, we have an indirect dependency. This happens quite quickly for larger components, like <c>Encodo.Security</c>. The screenshots below show a high-level snapshot of the indirect dependencies in the Encodo assembly and then also a detail view, with all sub-namespaces expanded. The detail view is much larger but shows you much more information about the exact nature of the cycle. When you select a red bounding box, another panel shows the full details and exact nature of the dependency. <img attachment="encodo_indirect_dependencies.png" align="left" caption="Dependency Cycles in Encodo (Overview)"><img attachment="encodo_dependency_cycles.png" align="left" caption="Dependency Cycles in Encodo (Detail)"><img attachment="encodo_dependency_cycles_dense_grid.png" align="left" caption="Encodo Dependency Cycles (Dense detail)"> <clear><h>Base Camp Two: base library <i>almost</i> cleaned up</h> <clear><img attachment="core_and_others.png" align="right-column" caption="App.Core.* and remaining dependencies"><img attachment="configuration_dependencies.png" align="right-column" caption="Configuration dependencies">After a bunch of work, I've managed to reduce the dependencies to a set of interfaces that are clearly far too dependent on many subsystems. <ul> <b>ICoreConfiguration:</b> references configuration options for optional subsystems like the software updater, the login, the incident reporter and more <b>ICoreFeedback:</b> references feedbacks for several optional processes, like software-update, logins and more <b>ICoreApplication:</b> references both the core configuration and feedback </ul> The white books for NDepend claim that <iq>[t]echnically speaking, the task of merging the source code of several assemblies into one is a relatively light one that takes just a few hours.</iq> However, this assumes that the code has already been properly separated into non-interdependent namespaces that correspond to components. These components can then relatively easily be extracted to separate assemblies. The issue that I have above with the Encodo assembly is a thornier one: the interfaces themselves embody a pattern that is inherently non-decoupling. I need to change how the configuration and feedback work completely in order to decouple this code. <clear><h>Roadmap for startup and configuration</h> To that end, I've created an issue in the issue-tracker for Quino, <a href="https://secure.encodo.ch/jira/browse/QNO-4659">QNO-4659</a><fn>, titled "Re-examine how the configuration, feedback and application work together". The design of these components predates our introduction of a service locator, which means it's much more tightly coupled (as you can see above). After some internal discussion, we've decided to change the design of the Encodo and Quino library support for application-level configuration and state. <dl dt_class="field"> Merge the <i>configuration</i> and <i>application</i> To date, the <i>configuration</i> has contained all of the information necessary to run an <i>application</i>. The configuration was more-or-less stateless and corresponded to the definition of an application, akin to how a <i>class</i> is the underlying stateless definition, while an <i>object</i> is an instance of that definition. In practice, though, we always use a single application per configuration and the distinction is irrelevant, for all practical purposes. This will simplify all referencing code, as we will no longer need to pass around an <c>IApplication<tconfiguration,></c>. Move the <i>feedback</i> to the service locator Instead of treating the feedback like a first-class citizen, with a direct reference on the application, make consumers use the service locator to retrieve an instance. This will remove the remaining generic argument in the definition of <c>IApplication</c>, leaving us with a base interface that is free of generic arguments. Move specific configuration objects to the service locator <div>The specific sub-interfaces that introduce dependencies are as follows:<ul> IncidentReporter SoftwareUpdater CommandSetManager LocationManager ConnectionSettingsManager </ul> Any components that currently reference the properties on the <c>ICoreConfiguration</c> can use the service locator to retrieve an instance instead. </div> Move specific settings to sub-objects <div>The configuration object is not only dependent on sub-objects, but is also overloaded with individual settings that are only used by very few specific sub-components. These will also be extracted into interfaces and moved into the service locator.<ul> ILoginConfiguration ISoftwareUpdateConfiguration IFileLogConfiguration</ul></div> </dl> As you can see, while NDepend is indispensable for <i>finding</i> dependencies, it can---along with a good refactoring tool (we use ReSharper)---really only help you clean up the low-hanging fruit. While I started out trying to split assemblies, I've now been side-tracked into cleaning up an older and less--well-designed component---and that's a very good thing. There are some gnarly knots that will feel nearly unsolvable---but with a good amount of planning, those can be re-designed as well. As I mentioned in the previous article, though, we can do so only because we're making a clean break from the 1.x version of Quino instead of trying to maintain backward compatibility. It's worth it, though: the new design already looks much cleaner and is much more easily explained to new developers. Once that rewrite is finished, the Encodo assembly should be clean and I'll use NDepend to find good places to split up that rather large assembly into sensible sub-assemblies. <hr> <ft>There is a setting to turn off showing the green dependencies---where the row depends on the column---to make it easier to read the matrix. If you do that, though, you have to make sure to select the class from which you're trying to remove dependencies in the column. For example, if class <c>A</c> and <c>B</c> are interdependent, but <c>A</c> should not rely on <c>B</c>, you should make sure <c>A</c> is showing in the column. You can then examine dependencies on row <c>B</c>---and then remove them. This works very nicely with both direct and indirect dependencies.</ft> <ft>This link is to the Quino issue tracker, which requires a login.</ft>