.Net Architecture Checker

V.1.7 © Harald M. Müller and Thomas Freudenberg
Overview
Used Software
License
Installation
Concepts
Usage
Examples of defining rules up-front—the preferred scenario
An example of a dependency detection session: Understanding the dependencies in Microsoft's Enterprise Library
Design considerations
Open Items
Release Notes
This project is hosted on SourceForge.net Logo.
The project homepage is at http://dotnetarchitect.sourceforge.net.
The development homepage ("sourceforge foundry") and download page is at https://sourceforge.net/projects/dotnetarchitect.
My email address is harald.m.mueller@bigfoot.com.

Overview

.Net Architecture Checker is a tool that helps you to keep the (static) architecture of a .Net software clean. You specify allowed dependencies between namespaces, classes, or even methods, and .Net Architecture Checker will check whether the dependencies are violated somewhere. You will use this in Nightly Builds or Continuous Integration Builds to prevent the introduction of unwanted dependencies.

Ideally, the software architect will specify the intended dependencies (or rather, dependency rules) for some module before code is written. However, by creating a diagram of the dependencies, it is also possible to explore and document the dependencies of an existing piece of software. Both ways of proceeding are especially useful to prevent the dreaded cyclic dependencies, which usually result in a tangled monolith of interdependencies, making a software unmaintainable in a quite short period of time.

.Net Architecture Checker has proven its usefulness (and stability) in a project of 25 developers with now more than 2 million LOC.

Used Software

.Net Architecture Checker is based on Microsoft's .NET Framework.
Internally, .Net Architecture Checker uses Mono's Cecil for .NET (version 0.6) from http://www.mono-project.com/Cecil.
For graph drawing, you need DOT from AT&T's GraphViz tool library at http://www.graphviz.org.

License

.Net Architecture Checker is licensed under the LGPL license (see http://www.opensource.org/licenses/lgpl-3.0.html).

Installation

Installation of executable and documentation

Prerequisites:
Steps:

Installation of sources

Concepts

Dependency rules and builds

.Net Architecture Checker reads one or more .Net assemblies and extracts all the uses of classes, structs, enums, delegates, inner classes, and methods from that file. It also needs a definition of dependency rules—here are two simple examples:
    MyProgram.** ---> YourLibrary.**
    MyProgram.** ---! System.Windows.Forms.**
The two asterisks ** are the abbreviation for "a class name, including a namespace." The two rules thus mean:
All classes (and enums, methods, ...) in and below the namespace MyProgram may use all classes (structs, delegates, ...) in and below the YourLibrary namespace.
All classes (etc.) in and below the namespace MyProgram must not use any class (etc.) from the System.Windows.Forms namespace tree.
The idea is that rules like these are defined by the software architect(s) and checked every time a build is run. When rules are violated, .Net Architecture Checker will write something like the following and return with exit code 3:
    **** Illegal dependency MyProgram.MyClass/NestedClass::.ctor ---> SomeOtherLibrary.Class
        (probably at src\MyProgram\MyClass.cs:80)
A violation of the rules should be like a compile time error: It breaks the build. Hence, a defined (static) software architecture can no longer degrade to some tangled web of dependencies, which will become unmaintainable over time.

Showing dependencies graphically

.Net Architecture Checker can also produce a DOT graph showing dependencies. This is not as useful as it sounds to most people: Defining and checking dependency rules should be the predominant goal. However, there are two applications for this feature:

Usage

Manual use

A typical call to .Net Architecture Checker looks as follows:
     DotNetArchitectureChecker /x=myrules.dep myproj*.dll myproj*.exe
This will output all the dependencies in all myproj* .Net assemblies in the current directory. It is good practice to have a naming convention for the assemblies of a project; by this, the project's own assemblies can easily be distinguished from other assemblies (usually copied ones) in the same directory. The complete usage of .Net Architecture Checker is explained here:
Usage:
   DotNetArchitectureChecker [<option> ...] [<assemblyfilespec> ...]

Typical uses:

* Check dependencies in My.DLL; My.dll.dep is somewhere below SourceDir:
      DotNetArchitectureChecker /s=SourceDir My.dll

* Produce graph of dependencies in My.DLL:
      DotNetArchitectureChecker /s=SourceDir My.DLL /g=My.dot
      dot -Tgif -oMy.gif My.dot

All messages of DotNetArchitectureChecker are written to Console.Out.

Options:
   /d=<directory>    For each assembly file A.dll, look for corresponding 
         rule file A.dll.dep in this directory (multiple /d options are 
         supported). This is especially useful with + lines.

   /s=<directory>    Like /d, but also look in all subdirectories. Mixing
         /s and /d options is supported.

   /x=<rule file>    Use this rule file if no matching rule file is found
         via /s and /d options. This is also useful if no /s and /d options
         are specified.

   /g=<dot file>   Create output of dependencies in AT&T DOT format.
         By default, DotNetArchitectureChecker tries to remove transitive
         edges - i.e., if a uses b, b uses c, but also a uses c, then
         the last edge is not shown. The algorithm for this will
         sometimes choose funny edges for removal ...

   /t    Show also transitive edges in DOT graph.

   /i[=<N>]        For each illegal edge (i.e., edge not allowed by 
         the dependency file), show an example of a concrete illegal 
         dependency in the DOT graph. N is the maximum width of strings 
         used; the default is 80. Graphs can become quite cluttered 
         with this option.

   /v    Verbose. Shows regular expressions used for checking and 
         all checked dependencies. Attention: Place /v BEFORE any
         /d, /s, or /x option to see the regular expressions.
         Produces lots of output.

   /y    Even more debugging output.

   /debug   Start with debugger.

Assemblyspecs - one of the following:
    
    simplefilename      the assembly is checked.
                        e.g. ProjectDir\bin\MyProject.Main.dll

    filepattern         all matching assemblies are checked.
                        e.g. bin\MyProject.*.dll 

    directory           all .DLL and .EXE files in the directory are checked.
                        e.g. MyProject\bin\Debug

    @filename           lines are read as assembly filenames and checked.
                        The file may contain empty lines, which are ignored.
                        e.g. @MyListOfFiles.txt

    <one of the above> /e <one of the above>            
                        The files after the /e are excluded from checking.
                        e.g. MyProject.*.dll /e *.vshost.*

Rules files:
         Rule files contain rule definition commands.
         The following commands are supported:

           empty line            ... ignored
           // comment            ... ignored
           # comment             ... ignored

           + filepath            ... include rules from that file. The path
                                     is interpreted relative to the current
                                     rule file.

           NAME := pattern       ... define abbreviation which is replaced
                                     in patterns before processing. NAME
                                     must be uppercase only (but it can
                                     contain digits, underscores etc.).
                                     Longer names are preferred to shorter
                                     ones during replacement. The pattern
                                     on the right side can in turn use 
                                     abbreviations. Abbreviation processing
                                     is done before all reg.exp. replacements
                                     described below.
                                     If an abbreviation definition for the 
                                     same name is encountered twice, it must
                                     define exactly the same value.

           pattern ---> pattern  ... allowed dependency. The second
                                     pattern may contain back-references
                                     of the form \1, \2 etc. that are
                                     matched against corresponding (...)
                                     groups in the first pattern.

           pattern ---! pattern  ... forbidden dependency. This can be used
                                     to exclude certain possibilities for
                                     specific cases instead of writing many
                                     "allowed" rules.

           pattern ---? pattern  ... questionable dependency. If a dependency
                                     matches such a rule, a warning will be
                                     emitted. This is useful for rules that
                                     should be removed, but have to remain
                                     in place for pragmatic reasons (only
                                     for some time, it is hoped).

           NAME :=
               <arbitrary lines except =:>
           =:                    ... definition of a rule macro. The
                                     arbitrary lines can contain the strings
                                     \L and \R, which are replaced with the
                                     corresponding patterns from the macro 
                                     use. NAME need not consist of letters
                                     only; also names like ===>, :::>, +++>
                                     etc. are allowed and quite useful.
                                     However, names must not be "too
                                     similar": If repeated characters are
                                     are replaced with a single one, they must
                                     still be different; hence, ===> and ====>
                                     are "too similar" and lead to an error.
                                     As with abbreviations, if a macro 
                                     definition for the same name is 
                                     encountered twice, it must define 
                                     exactly the same value.

           pattern NAME pattern  ... Use of a defined macro.

           % pattern (with at least one group) 
                                 ... Define output in DAG graph (substring
                                     matching first group is used as label).
                                     If the group is empty, the dependency
                                     is not shown in the graph.
                                     Useful only with /d option.

         For an example of a dependency file, see near end of this help text.

         A pattern can be specified in three ways:

           ^regexp$              ... matched against a declaration
                                     ("declarations" see below)

           ^regexp               ... the regexp is expanded to up to four
                                     different forms, all of which are
                                     matched against declarations:
               ^regexp$                   - for matching a class name
               ^regexp(/<ident>)*$        - for matching nested classes
                                            (if regexp contains no / )
                                            <ident> is the pattern
                                            matching an identifier.
               ^regexp::<ident>$          - for matching methods
                                            (if regexp contains no ::)
               ^regexp(/<ident>)*::ident$ - for methods of nested classes
                                            (if regexp contains no / and no ::)

           wildcardpath          ... first, the following replacements are done:

               .       is replaced with the reg.exp. [.] (matches single period)

               *       is replaced with the reg.exp. for an <ident> (a "name")

               **      is usually replaced with <ident>(?:.<ident>)* (a 
                       "path").
                            (?: in a reg.exp.means that the parentheses do not 
                            count as numbered group when matching \1, \2, etc.)
                       However, if there is a slash (/) somewhere to the left 
                       of the **, it is replaced with <ident>(?:/<ident>)*, 
                       i.e., the idents are separated by /. This can be used
                       to match inner class hierarchies.

               After the wildcard replacemants, suffixes are added as for 
               ^regexp.



Example of a dependency file with some important dependencies (all
using the wildcardpath syntax):

   // Every class may use all classes from its own namespace.
        (**).* ---> \1.*

   // Special dependency for class names without namespace
   // (the pattern above will not work, because it contains a
   // period): A class from the global namespace may use
   // all classes from that namespace.
        * ---> *

   // Every class may use all classes from child namespaces
   // of its own namespace.
        (**).* ---> \1.**.*

   // Every class may use all of System.
        ** ---> System.**

   // Use ALL as abbreviation for MyProgram.**
        ALL := MyProgram.**

   // All MyProgram classes must not use Windows Forms
   // (even though in principle, all classes may use all of 
   // System according to the previous ---> rule).
        ALL ---! System.Windows.Forms.**

   // All MyProgram classes may use classes from antlr.
        ALL ---> antlr.**

   // In DAG output, identify each object by its path (i.e.
   // namespace).
        % (**).*

   // Classes without namespace are identified by their class name:
        % (*)

   // Classes in System.* are identified by the empty group, i.e.,
   // they (and arrows reaching them) are not shown at all.
        % ()System.**

Exit codes:
   0    All dependencies ok (including questionable rules).
   1    Usage error.
   2    Cannot load dependency file (syntax error or file not found).
   3    Dependencies not ok.
   4    Assembly file specified as argument not found.
   5    Other exception.
   6    No dependency file found for an assembly in /d and /s 
        directories, and /x not specified.

Use in builds

In a build, you will call .Net Architecture Checker.EXE, and possibly DOT separately.

For uses in builds, DotNetArchitectureChecker.EXE has various exit codes. Essentially, you will test for exit code = 0—this means that everything is ok; exit code = 3 is also interesting, meaning "some dependencies did not follow the rules." For the other exit codes, see the above copy of the help output of DotNetArchitectureChecker.EXE.

Examples of defining rules up-front—the preferred scenario

Namespace dependency rules

Here is an example scenario for dependency checking. Assume you are responsible for the design of a new GUI library which uses the MVC pattern. Let's call that new library "Yet Another GUI Library" (or "Your Advanced GUI Library"), which is abbreviated as "YAGL." The architect (you) has a vision of an architecture which decouples the implementations of the three pattern roles as cleanly as possible—preferably by namespaces. To be concrete, you start with the design and implementation of buttons. You decide that you want the following namespaces:

and, moreover Your next step is to decide on the dependencies that you want to allow. This will lead to the intended decoupled architecture, where e.g. the Models namespace can be replaced (or even removed) without disturbing the rest of the design (except that, at runtime, there must of course be some concrete models around—but they could be provided by other modules, e.g. an application or a data binding layer). Here are the rules that might be necessary with the namespace division above:
    // MODELS:
      // Abstract and standard models use the model interfaces
    Yagl.Buttons.Models.**          ---> Yagl.Buttons.IModels.**
    
    // CONTROLLERS:
      // Controllers call View changing methods - on a general level ...
    Yagl.Buttons.Controllers.*      ---> Yagl.Buttons.Views.*
      // ... and for each GUI system (but not crossing
      // GUI systems!)
    Yagl.Buttons.Controllers.(*).** ---> Yagl.Buttons.Views.\1.**
    
      // GUI-system dependent Controllers use common Controller code
    Yagl.Buttons.Controllers.*.**   ---> Yagl.Buttons.Controllers.*
    
      // Controllers for one GUI system use methods and events from the 
      // corresponding native library.
    Yagl.Buttons.Controllers.(*).** ---> System.GUISupport.\1.**
    
      // Controllers influence Models; and hook themselves as listeners to Models
    Yagl.Buttons.Controllers.*      ---> Yagl.Buttons.IModels.**
    
    // VIEWS:
      // Views hook themselves as listeners to Models
    Yagl.Buttons.Views.*            ---> Yagl.Buttons.IModels.**
    
      // GUI-system dependent Views use common View code
    Yagl.Buttons.Views.*.**         ---> Yagl.Buttons.Views.*
    
      // Views for one GUI system use methods and events from the 
      // corresponding native library.
    Yagl.Buttons.View.(*).**        ---> System.GUISupport.\1.**

The last rule (but not only that one) shows how the right side of a dependency rule can reference the left side by using "back references" (this is due to an idea of Ralf Kretzschmar, a colleague on a project I'm currently working on). As in standard regular expressions, \1 references the first parenthesized group on the left side, \2 the second etc.

Your team will now start designing the classes and algorithms and then code and test along. As you have .Net Architecture Checker in your continuous build, there is no chance that someone inadvertently introduces unwanted dependencies. At some point (which might be quite early if you practice TDD) you want to assemble a small button with a controller, a view, and a model. At this time, you will probably notice that you can build this aggregate component with your defined dependencies in any of the existing namespaces, as you need access to the class constructors of controllers and views as well as models. You should now not weaken the dependencies: This will, if done a few more times, lead to a tangled architecture, where almost everything depends on everything.

One possibility of a correct design which keeps the dependencies clean is that you define an additional namespace

which provides typical facade classes connecting elements from all namespaces above. The dependencies of this namespace would (or could) be like this:

      // needed to create standard model via constructor
    Yagl.Buttons.Standard.* ---> Yagl.Buttons.Models.**
      // needed to define instance vars to models
    Yagl.Buttons.Standard.* ---> Yagl.Buttons.IModels.**
      // needed to create GUI-dependent controller via constructor
    Yagl.Buttons.Standard.* ---> Yagl.Buttons.Controllers.*.**
      // needed to create GUI-dependent view via constructor
    Yagl.Buttons.Standard.* ---> Yagl.Buttons.Views.*.**

—and now you are happy: Facade classes RadioButton, ToggleButton, StandardButton, MenuButton and more can be defined in the Standard namespace by instantiating objects from the Models, Views.*, and Controllers.* namespaces.

A different concept is that instead of providing a "connecting namespace," you use dependency injection: The creation is externalized into a container (e.g. PicoContainer), which is suitably configured. In some sense, that configuration takes the role of the connecting namespace.

In later steps of designing YAGL, you might generalize the patterns above for all sorts of GUI controls, so that you end up with dependencies like

    Yagl.(*).Models.**          ---> Yagl.\1.IModels.**
    Yagl.(*).Controllers.*      ---> Yagl.\1.Views.*
    Yagl.(*).Controllers.(*).** ---> Yagl.\1.Views.\2.**
    Yagl.(*).Controllers.*.**   ---> Yagl.\1.Controllers.*
etc.

Macros

In the previous example, you might have defined namespaces Yagl.Buttons, Yagl.Labels, Yagl.Grids etc. with the corresponding sub-namespaces (Models, IModels, Controllers, Views). Between such "modules", you might also have dependencies: However, the dependencies between these module should follow a consistent pattern: In larger systems, such compound rules introduce an additional level of abstraction, which helps to define the constraints more concisely. Here is an example of definitions for Yagl, which uses a macro called ===>:
    ===> :=
        Yagl.\L.(*).**             ---> Yagl.\R.\1.**
        Yagl.\L.Models.**          ---> Yagl.\R.IModels.**
        Yagl.\L.Controllers.*      ---> Yagl.\R.Views.*
        Yagl.\L.Controllers.(*).** ---> Yagl.\R.Views.\1.**
        Yagl.\L.Controllers.*.**   ---> Yagl.\R.Controllers.*
    =:
    
    Buttons ===> Labels
    Grids   ===> Buttons
    Grids   ===> Labels

Method dependency rules

Here is a short explanation of a quite different dependency scenario, this time on the level of methods: An object-relational mapping I wrote does its work for searches in multiple phases (this is a sort of pipeline architecture):

  1. First, search conditions are converted to an internal tree representation (c2t—"condition to tree")
  2. Then, the trees are enhanced with SQL statements, depending on lazy/eager load and other factors (t2s—"tree to SQL")
  3. Then, the SQL statements are executed (s2r—"SQL to result sets")
  4. Finally, the result sets are converted to objects (r2o—"result sets to objects")
Many internal classes contribute to more than one of these phases: There is state, and there are methods for each phase. For a clean architecture, methods of one phase should only call methods and access fields provided by the same phase; this is important so that methods of earlier phases (e.g. a t2s method) do not call methods of later phases (e.g. an r2o method), when the state necessary for the later phase has not yet been computed by an earlier phase! In addition, there are some common methods which may be called in each phase (e.g. property getters)—these are to be suffixed with _com. To ascertain these constraints, the methods' names are to be suffixed with the phase, e.g. ComputeSQL_t2s() or CreateObject_r2o(...). Here are possible dependency rules that help to maintain the calling architecture:
    ORMapping.**::*_c2t  --->   ORMapping.**::*_c2t 
    ORMapping.**::*_t2s  --->   ORMapping.**::*_t2s
    ORMapping.**::*_s2r  --->   ORMapping.**::*_s2r 
    ORMapping.**::*_r2o  --->   ORMapping.**::*_r2o
    ORMapping.**::*      --->   ORMapping.**::*_com
      // Methods may access private state (prefixed with _).
      // A separate getter is provided for each phase that
      // may legitimally access some field.
    ORMapping.**::*      --->   ORMapping.**::_*
    
      // ORMapping may use System except Windows.Forms
    ORMapping.**         --->   System.**
    ORMapping.**         ---!   System.Windows.Forms.**
      // Every class may use <PrivateImplementationDetails>
    ORMapping.** ---> <PrivateImplementationDetails>**
By the way, a shorter way of defining the first 4 rules would be:
    ORMapping.**::*_(c2t|t2s|s2r|r2o)  --->   ORMapping.**::*_\1

However, this is probably more difficult to understand than the expanded version above.

Also, in practice, the rules need to include methods for writing; and they will be more elaborate, as there will also be dependency rules between namespaces inside the ORMapping. E.g., some classes responsible for writing to the database—INSERT, UPDATE, DELETE—probably must not call, and not be called by, classes for querying the database.

 

The two scenarios just described are typical and intended uses for dependency checking: You (as the architect) define the dependencies up-front and have them checked during the life-time of the project. Of course, new developments and experiences might make it necessary to add new dependencies (as new modules with new namespaces or new method types are designed), and, sometimes, also the addition and modification of dependencies for existing namespaces. However, the latter can be a quite disruptive act as it might fundamentally shake the architectural foundations of the software system. When you become (or if you already are) a seasoned software architect, you will create more and more stable architectures up-front—or (more important) learn when to delay architectural decisions so that they can be arrived at when the necessary knowledge and understanding is available.

An example of a dependency detection session: Understanding the dependencies in Microsoft's Enterprise Library (January 2006)

In real life, you will at times be confronted with existing software where the architecture is not clear. .Net Architecture Checker gives you the possibility to draw diagrams of dependencies with arbitrary granularity, which can help to understand the dependencies and architecture. The goal of such understanding should always be to define the rules to be followed from that point on—probably after some clean-up (re-architecting) of the software under consideration. As a concrete example, let us find out the main dependencies in Microsoft's Enterprise Library of January 2006.

We start with graphing the main modules. Because we practice whar might be called "explorative architecturing," we allow all dependencies. We store the following in a file, say msel.dep:

    ** ---> **
    
      // Show all leaf namespaces in graph
    % (Microsoft.Practices.*).**
    
      // For System and rest of Microsoft, show only one box
    % (System).**
    % (Microsoft).**
We call dotnetarchitect.bat as follows:
    DotNetArchitectureChecker /x=msel.dep /g=msel.gif "c:\Programme\Microsoft Enterprise Library January 2006\bin\*.dll"

Here is the resulting diagram in msel.gif ...

A small disappointment: We were too high-level! In the next try, we go one level deeper:

    ** ---> **
    
      // Show two levels below Microsoft.Practices - especially,
      // below Microsoft.Practices.EnterpriseLibrary.
    % (Microsoft.Practices.*.*).**
    % (Microsoft.Practices.*).*
    
      // For System and rest of Microsoft, show only one box
    % (System).**
    % (Microsoft).**

The same call as above now yields the following diagram:

Now we see for the first time the library's internal structure. The only obvious architectural flaw is the cyclic dependency between Caching and Security; we will explore this further in the next steps. Please remember that this diagram excludes as many transitive edges as possible—hence it might e.g. be that classes from Logging use classes from Common etc. To show the full dependencies for demonstrating the difference, let's call .Net Architecture Checker with the /t option (/t=transitive edges):

    DotNetArchitectureChecker /x=msel.dep /g=msel.gif /t "c:\Programme\Microsoft Enterprise Library January 2006\bin\*.dll"

The result is typical for a weakly layered architecture and demonstrates why the removal of transitive edges helps to understand the actual dependencies. However, we will have to return to this type of diagram later. For now, let us continue with the exploration of the cycle between Caching and Security. We only show Caching and Security, but now down to the lowest namespaces. To distinguish visually between the two, we have Caching namespaces shown in a "long" format including "Practices.EnterpriseLibrary," whereas namespaces from Security are shown in a "short" format:

    ** ---> **
    
      // Show Caching and Security in detail.
    % Microsoft.(Practices.EnterpriseLibrary.Caching.**).*
    % Microsoft.Practices.EnterpriseLibrary.(Security.**).*
    
      // For all the rest, show nothing
    % ()**

This diagram certainly needs some detailed inspection. However, after a short time it becomes clear that Caching.Cryptography and its sub-namespaces —and only those—use modules from Security. Hence, we now reformulate the graph patterns for the complete Enterprise Library:

    ** ---> **
    
      // Show two levels below Microsoft.Practices - especially,
      // below Microsoft.Practices.EnterpriseLibrary.
      // Caching.Cryptography is singled out, because it depends on Security.
    % (Microsoft.Practices.*.*).**
    % (Microsoft.Practices.*).*
    % (Microsoft.Practices.EnterpriseLibrary.Caching.Cryptography).**
    
      // For System and rest of Microsoft, show only one box
    % (System).**
    % (Microsoft).**

Here is the resulting picture:

Now that we have found a dependency architecture which is ok, we want to codify the dependency rules so that subsequent development cannot violate these rules. To this end, we must first show also all the transitive edges. We are no longer interested in System, because this may be used by all according to the standard rules. Therefore, we use the following graph patterns and call .Net Architecture Checker with the /t option:

    ** ---> **
    
      // Show two levels below Microsoft.Practices - especially,
      // below Microsoft.Practices.EnterpriseLibrary.
      // Caching.Cryptography is singled out, because it depends on Security.
    % (Microsoft.Practices.*.*).**
    % (Microsoft.Practices.*).*
    % (Microsoft.Practices.EnterpriseLibrary.Caching.Cryptography).**
    
      // Do not show System; for rest of Microsoft, show only one box
    % ()System.**
    % (Microsoft).**

The resulting graph shows us, to no-one's surprise, that Common and Configuration are used by everyone. Moreover, ObjectBuilder is also used by everyone (which is stated in the design documentation of the Enterprise Library somewhere). For the rest, we have a not too large set of specific dependencies which are easily written down. Here is the resulting list:

      // Everyone in EL may use ObjectBuilder, EL.Common, and EL.Configuration
    Microsoft.Practices.EnterpriseLibrary.**                      ---> Microsoft.Practices.ObjectBuilder.**
    Microsoft.Practices.EnterpriseLibrary.**                      ---> Microsoft.Practices.EnterpriseLibrary.Common.**
    Microsoft.Practices.EnterpriseLibrary.**                      ---> Microsoft.Practices.EnterpriseLibrary.Configuration.**
    
      // EL.Logging and EL.Caching may use EL.Data
    Microsoft.Practices.EnterpriseLibrary.Logging.**              ---> Microsoft.Practices.EnterpriseLibrary.Data.**
    Microsoft.Practices.EnterpriseLibrary.Caching.**              ---> Microsoft.Practices.EnterpriseLibrary.Data.**
    
      // EL.Security may use EL.Caching, but not EL.Caching.Cryptography
    Microsoft.Practices.EnterpriseLibrary.Security.**             ---> Microsoft.Practices.EnterpriseLibrary.Caching.**
    Microsoft.Practices.EnterpriseLibrary.Security.**             ---! Microsoft.Practices.EnterpriseLibrary.Caching.Cryptography.**
    
      // EL.Caching.Cryptography may use EL.Security (the "backwards dependency"
      // which created the loop we saw a few steps ago).
    Microsoft.Practices.EnterpriseLibrary.Caching.Cryptography.** ---> Microsoft.Practices.EnterpriseLibrary.Security.**
    
      // EL.ExceptionHandling may use EL.Logging
    Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.**    ---> Microsoft.Practices.EnterpriseLibrary.Logging.**
As the string Microsoft.Practices.EnterpriseLibrary is occurring quite often here, it may make sense to replace it with a shorter abbreviation, e.g. as follows:
    _EL := Microsoft.Practices.EnterpriseLibrary

      // Everyone in EL may use ObjectBuilder, EL.Common, and EL.Configuration
    _EL.**                      ---> Microsoft.Practices.ObjectBuilder.**
    _EL.**                      ---> _EL.Common.**
    _EL.**                      ---> _EL.Configuration.**
    
      // EL.Logging and EL.Caching may use EL.Data
    _EL.Logging.**              ---> _EL.Data.**
    _EL.Caching.**              ---> _EL.Data.**
    
      // EL.Security may use EL.Caching, but not EL.Caching.Cryptography
    _EL.Security.**             ---> _EL.Caching.**
    _EL.Security.**             ---! _EL.Caching.Cryptography.**
    
      // EL.Caching.Cryptography may use EL.Security (the "backwards dependency"
      // which created the loop we saw a few steps ago).
    _EL.Caching.Cryptography.** ---> _EL.Security.**
    
      // EL.ExceptionHandling may use EL.Logging
    _EL.ExceptionHandling.**    ---> _EL.Logging.**

Running .Net Architecture Checker gives us ... what is that? Zillions of "Illegal dependencies!" What's wrong here?

Here is the first "illegal" dependency:

  **** Illegal dependency Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.Instrumentation.DistributorEventLogger::LogServiceStarted ---> Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.Properties.Resources::get_Culture

What does Logging use here? Here is a more concise view of the dependency (you might want to highlight this with a marker on a printout of the "illegal" dependencies):

  ...Logging.MsmqDistributor.Instrumentation... ---> ...Logging.MsmqDistributor.Properties...
It is clear now: We forgot to specify that we are not concerned about arbitrary dependencies inside toplevel namespaces of the Enterprise Library. Hence, we need the following additional rule:
      // Everyone in or below a namespace EL.* may use all other namespaces in or below the same top-level EL namespace 
    Microsoft.Practices.EnterpriseLibrary.(*).**   ---> Microsoft.Practices.EnterpriseLibrary.\1.**
But we would now also allow that Caching uses all of Caching.Cryptography! Although this is not the case today, we want to prevent it in the future (so that someone will not introduce a cyclic dependency with Security!). Let us disect Caching to find out about the concrete rules we want to allow. Here are the graph patterns for this job:
    ** ---> **
    
      // Show only Caching (top level and one level below) so
      // that we can find out the dependencies needed in there.
    % (_EL.Caching).*
    % (_EL.Caching.*).**
    
    % ()Microsoft.**
    % ()System.**

The result—with transitive edges—is as follows:

Thus, we now know which other namespaces are immediately below Caching. For all these—except Cryptography—, and also for Caching itself, we suppress the use Caching.Cryptography:

    _EL.Caching.*                ---! _EL.Caching.Cryptography.**
    _EL.Caching.BackingStoreImplementations.** ---! _EL.Caching.Cryptography.**
    _EL.Caching.Common.**        ---! _EL.Caching.Cryptography.**
    _EL.Caching.Configuration.** ---! _EL.Caching.Cryptography.**
    _EL.Caching.Database.**      ---! _EL.Caching.Cryptography.**
etc.

However, this is not at all a fool-proof set of of rules: If a new sub-namespace of Caching is introduced at some future time, that future namespace could use Cryptography. An alternative idea is to decree that all namespaces not starting with C cannot use Cryptography:

    _EL.Caching.*                ---! _EL.Caching.Cryptography.**
    _EL.Caching.[^C]*.**         ---! _EL.Caching.Cryptography.**
However, this would still allow Caching.Common and Caching.Configuration to use Caching.Cryptography. So, we add another rule:
    _EL.Caching.C[^r]*.**        ---! _EL.Caching.Cryptography.**

—and we could add more rules to suppress use of Cryptography by longer and longer prefixes.

In my opinion, a better idea would be that Microsoft changed the name of Caching.Cryptography to e.g. SecureCaching in the Enterprise Library: Then there would be a clear top-level acyclic dependency graph.

Together with the other rules, we get the following overall dependency rule list:

      // Everyone in or below a namespace EL.* may use all other namespaces in or below the same namespace
    _EL.(*).**                   ---> _EL.\1.**

      // However, top-level Caching and Caching sub-namespaces not starting with Cry must not use Caching.Cryptography
    _EL.Caching.*                ---! _EL.Caching.Cryptography.**
    _EL.Caching.[^C]*.**         ---! _EL.Caching.Cryptography.**
    _EL.Caching.C[^r]*.**        ---! _EL.Caching.Cryptography.**
    _EL.Caching.Cr[^y]*.**       ---! _EL.Caching.Cryptography.**

      // Everyone in EL may use ObjectBuilder, EL.Common, and EL.Configuration
    _EL.**                       ---> Microsoft.Practices.ObjectBuilder.**
    _EL.**                       ---> _EL.Common.**
    _EL.**                       ---> _EL.Configuration.**
    
      // EL.Logging and EL.Caching may use EL.Data
    _EL.Logging.**               ---> _EL.Data.**
    _EL.Caching.**               ---> _EL.Data.**
    
      // EL.Security may use EL.Caching, but not EL.Caching.Cryptography
    _EL.Security.**              ---> _EL.Caching.**
    _EL.Security.**              ---! _EL.Caching.Cryptography.**
    
      // EL.Caching.Cryptography may use EL.Security (the "backwards dependency")
    _EL.Caching.Cryptography.**  ---> _EL.Security.**
    
      // EL.ExceptionHandling may use EL.Logging
    _EL.ExceptionHandling.**     ---> _EL.Logging.**

A final run of .Net Architecture Checker over the Enterprise Library now yields no messages—hence, our rules encompass all current dependencies. On the other hand, they are sufficiently strong to prevent future corruption of the static architecture during maintenance and redesign.

For teaching developers about the allowed dependencies, I would start with the clean picture to show the principial layering of the library:

For detailed dependencies between modules, the architect could then

or, preferably,

Design considerations

A. Couldn't there be an option showing all cyclic dependencies?— —The idea is that architecture is not arrived at by "reacting to symptoms" (e.g. to cycles), but on the contrary by "acting," i.e., by wanting that certain dependencies may occur, whereas others do not occur. Thus, the goal is not that a tool emits warnings, but that an architect prescribes allowed dependency patterns. These might also include cycles at certain or even many places. The object-relational mapping mentioned above, for example, had—intentionally—almost no restrictions on class dependencies, but very restrictive rules for method dependencies.

B. Couldn't the dependency rule language allow for "except rules," e.g., "All sub-namespaces of A except A.B may use C"?— —

Open items

1. Provide an MSBuild task to run .Net Architecture Checker in automated builds.

2. Make .Net Architecture Checker multi-threaded to increase performance on multi-core and multi-processor machines.

Release Notes

1.7 (2010-06-07)

1.6 (2010-05-10)

1.5 (2010-05-10)

1.4 (2010-04-27)

1.3 (2010-04-25)

1.2 (2010-04-19)

1.1 (2010-04-17)

1.02 (2010-01-21)

1.01 (2010-01-19)

1.0 (2009-12-29)

© 2006-2009 HMMüller and Thomas Freudenberg