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


Splitting up assemblies in Quino using NDepend (Part I)


<img src="{att_link}quino_logo_no_text-small.png" href="{att_link}quino_logo_no_text-small.png" align="left" scale="50%">A lot of work has been put into Quino 2.0<fn>, with almost no stone left unturned. Almost every subsystem has been refactored and simplified, including but not limited to the data driver, the schema migration, generated code and metadata, model-building, security and authentication, service-application support and, of course, configuration and execution. Two of the finishing touches before releasing 2.0 are to <b>reorganize</b> all of the code into a more coherent <b>namespace</b> structure and to <b>reduce the size</b> of the two monolithic <b>assemblies</b>: Encodo and Quino. <clear><h>A Step Back</h> The first thing to establish is: why are we doing this? Why do we want to reduce dependencies and reduce the size of our assemblies? There are several reasons, but a major reason is to improve the <b>discoverability</b> of patterns and types in Quino. Two giant assemblies are not inviting---they are, in fact, daunting. Replace these assemblies with dozens of smaller ones and users of your framework will be more likely to (A) find what they're looking for on their own and (B) build their own extensions with the correct dependencies and patterns. Neither of these is guaranteed, but smaller modules are a great start. Another big reason is <b>portability</b>. The .NET Core was released as open-source software some time ago and more and more .NET source code is added to it each day. There are portable targets, non-Windows targets, Universal-build targets and much more. It makes sense to split code up into highly portable units with as few dependencies as possible. That is, the dependencies should be explicit and intended. Not only that, but NuGet <b>packaging</b> has come to the fore more than ever. Quino was originally designed to keep third-party boundaries clear, but we wanted to make it as easy as possible to use Quino. Just include Encodo and Quino and off you went. However, with NuGet, you can now say you want to use Quino.Standard and you'll get Quino.Core, Encodo.Core, Encodo.Services.SimpleInjector, Quino.Services.SimpleInjector and other packages. With so much interesting code in the Quino framework, we want to make it available as much as possible not only for our internal projects but also for customer projects where appropriate and, also, possibly for open-source distribution. <h>NDepend</h> I've used NDepend before<fn> to clean up dependencies. However, the last analysis I did about a year ago showed quite deep problems<fn> that needed to be addressed before any further dependency analysis could bear fruit at all. With that work finally out of the way, I'm ready to re-engage with NDepend and see where we stand with Quino. As luck would have it, NDepend is in version 6, released at the start of summer 2015. As was the case last year, NDepend has generously provided me with an upgrade license to allow me to test and evaluate the new version with a sizable and real-world project. Here is some of the <a href="" source="Twitter">feedback I sent to NDepend</a>: <bq quote-style="none"> I really, really like the depth of insight NDepend gives me into my code. I find myself thinking "SOLID" much more often when I have NDepend shaking its head sadly at me, tsk-tsking at all of the dependency snarls I've managed to build. <ul> It's fast and super-reliable. I can work these checks into my workflow relatively easily. I'm using the matrix view a lot more than the graphs because even NDepend recommends I don't use a graph for the number of namespaces/classes I'm usually looking at Where the graph view is super-useful is for examining *indirect* dependencies, which are harder to decipher with the graph I've found so many silly mistakes/lazy decisions that would lead to confusion for developers new to my framework I'm spending so much time with it and documenting my experiences because I want more people at my company to use it I haven't even scratched the surface of the warnings/errors but want to get to that, as well (the Dashboard tells me of 71 rules violated; 9 critical; I'm afraid to look :-) </ul> </bq> <h>Use Cases</h> Before I get more in-depth with NDepend, please note that there at least two main use cases for this tool<fn>: <ol> Clean up a project or solution that has never had a professional dependency checkup Analyze and maintain separation and architectural layers in a project or solution </ol> These two use cases are vastly different. The first is like cleaning a gas-station bathroom for the first time in years; the second is more like the weekly once-over you give your bathroom at home. The tools you'll need for the two jobs are similar, but quite different in scope and power. The same goes for NDepend: how you'll use it to claw your way back to architectural purity is different than how you'll use it to occasionally clean up an already mostly-clean project. Quino is much better than it was the last time we peeked under the covers with NDepend, but we're still going to need a bucket of industrial cleaner before we're done.<fn> The first step is to make sure that you're analyzing the correct assemblies. Show the project properties to see which assemblies are included. You should remove all assemblies from consideration that don't currently interest you (especially if your library is not quite up to snuff, dependency-wise; afterwards, you can leave as many clean assemblies in the list as you like).<fn> <h>Industrial-strength cleaner for Quino</h> Running an analysis with NDepend 6 generates a nice report, which includes the following initial dependency graph for the assemblies. <img src="{att_link}componentdependenciesdiagram.png" href="{att_link}componentdependenciesdiagram.png" align="none" caption="Assembly dependencies for Encodo and Quino" scale="50%"> As you can see, Encodo and Quino depend only on system assemblies, but there are components that pull in other references where they might not be needed. The initial dependency matrices for Encodo and Quino both look much better than they did when I last generated one. The images below show what we have to work with in the Encodo and Quino assemblies. <img attachment="initial_dependency_matrix_for_encodo.png" align="left" caption="Initial Dependency Matrix for Encodo"><img attachment="initial_dependency_matrix_for_quino.png" align="left" caption="Initial Dependency Matrix for Quino"> <clear>It's not as terrible as I've made out, right? There is far less namespace-nesting, so it's much easier to see where the bidirectional dependencies are. There are only a handful of cyclic dependencies in each library, with Encodo edging out Quino because of (A) the nature of the code and (B) I'd put more effort into Encodo so far. I'm not particularly surprised to see that this is relatively clean because we've put effort into keeping the <i>external</i> dependencies low. It's the internal dependencies in Encodo and Quino that we want to reduce. <h>Small and Focused Assemblies</h> <img src="{att_link}architecture_graph_for_quino-beta3.png" href="{att_link}architecture_graph_for_quino-beta3.png" align="right-column" caption="(Partial) Architecture Graph for Quino 2.0-beta3" scale="30%"><img src="{att_link}encodo_assemblies_2_0.png" align="right-column" caption="Encodo assemblies for Quino 2.0-beta3"><img src="{att_link}quino_assemblies_2_0.png" align="right-column" caption="Quino assemblies for Quino 2.0-beta3">The goal, as stated in the title of this article, is to split Encodo and Quino into separate assemblies. While removing cyclic dependencies is <i>required</i> for such an operation, it's not <i>sufficient</i>. Even without cycles, it's still possible that a given assembly is still <i>too dependent</i> on other assemblies. Before going any farther, I'm going to list the assemblies we'd like to have. By "like to have", I mean the list that we'd originally planned plus a few more that we added while doing the actual splitting.<fn> The images on the right show the assemblies in Encodo, Quino and a partial overview of the dependency graph (calculated with the ReSharper Architecture overview rather than with NDepend, just for variety). Of these, the following assemblies and their dependencies are of particular interest<fn>: <ul> <b>Encodo.Core</b>: System dependencies only <b>Encodo.Application</b>: basic application support<fn> <b>Encodo.Application.Standard</b>: configuration methods for non-metadata applications that don't want to pick and choose packages/assemblies <b>Encodo.Expressions</b>: depends only on <c>Encodo.Core</c> <b>Quino.Meta</b>: depends only on <c>Encodo.Core</c> and <c>Encodo.Expressions</c> <b>Quino.Meta.Standard</b>: Optional, but useful metadata extensions <b>Quino.Application</b>: depends only on <c>Encodo.Application</c> and <c>Quino.Meta</c> <b>Quino.Application.Standard</b>: configuration methods for metadata applications that don't want to pick and choose packages/assemblies <b>Quino.Data</b>: depends on <c>Quino.Application</c> and some Encodo.* assemblies <b>Quino.Schema</b>: depends on <c>Quino.Data</c> </ul> This seems like a good spot to stop, before getting into the nitty-gritty detail of how we used NDepend in practice. In the next article, I'll discuss both the high-level and low-level workflows I used with NDepend to efficiently clear up these cycles. Stay tuned! <hr> <ft> Release notes for 2.0 betas: <ul> <a href="{app}view_article.php?id=411">v2.0-beta1: Configuration, services and web</a> <a href="{app}view_article.php?id=415">v2.0-beta2: Code generation, IOC and configuration</a> </ul> Articles about design: <ul> <a href="{app}view_article.php?id=412">Encodo’s configuration library for Quino: part I</a> <a href="{app}view_article.php?id=413">Encodo’s configuration library for Quino: part II</a> <a href="{app}view_article.php?id=414">Encodo’s configuration library for Quino: part III</a> <a href="{app}view_article.php?id=422">API Design: Running an Application (Part I)</a> <a href="{app}view_article.php?id=426">API Design: To Generic or not Generic? (Part II)</a> </ul> </ft> <ft> I published a two-parter in August and November of 2014. <ul> <a href="{app}/view_article.php?id=390">The Road to Quino 2.0: Maintaining architecture with NDepend (part I)</a> <a href="{app}/view_article.php?id=400">The Road to Quino 2.0: Maintaining architecture with NDepend (part II)</a></ul> </ft> <ft>You can see a lot of the issues associated with these changes in the release notes for <a href="{app}view_article.php?id=411">Quino 2.0-beta1</a> (mostly the first point in the "Highlights" section) and <a href="{app}view_article.php?id=415">Quino 2.0-beta2</a> (pretty much all of the points in the "Highlights" section).</ft> <ft>I'm sure there are more, but those are the ones I can think of that would apply to my project (for now).</ft> <ft> stretch the gas-station metaphor even further.</ft> <ft> Here I'm going to give you a tip that confused me for a while, but that I think was due to particularly bad luck and is actually quite a rare occurrence. <info> If you already see the correct assemblies in the list, you should still check that NDepend picked up the right paths. That is, if you haven't followed the advice in NDepend's white paper and still have a different <c>bin</c> folder for each assembly, you may see something like the following in the tooltip when you hover over the assembly name: <bq>Several valid .NET assemblies with the name {Encodo} have been found. They all have the same version. the one with the biggest file has been chosen.</bq> If NDepend has accidentally found an older copy of your assembly, you must delete that assembly. Even if you add an assembly directly, NDepend will <i>not</i> honor the path from which you added it. This isn't as bad as it sounds, since it's a very strange constellation of circumstances that led to this assembly hanging around anyway: <ul> The project is no longer included in the latest Quino but lingers in my workspace The version number is unfortunately the same, even though the assembly is wildly out of date </ul> I only noticed because I <i>knew</i> I didn't have that many dependency cycles left in the Encodo assembly. </info> </ft> <ft>Especially for larger libraries like Quino, you'll find that your expectations about dependencies between modules will be largely correct, but will still have gossamer filaments connecting them that prevent a clean split. In those cases, we just created new assemblies to hold these common dependencies. Once an initial split is complete, we'll iterate and refactor to reduce some of these ad-hoc assemblies.</ft><ft>Screenshots, names and dependencies are based on a pre-release version of Quino, so while the likelihood is small, everything is subject to change.</ft> <ft>Stay tuned for an upcoming post on the details of starting up an application, which is the support provided in <c>Encodo.Application</c>.</ft>