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 index1.gif:

index1.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:

index2.gif

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"


index2transitive.gif

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
    % ()**


index3.gif

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:

index4.gif

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).**


index5.gif

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:

index7.gif

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:

index4.gif

For detailed dependencies between modules, the architect could then
  • show on a printout of the diagram above which group of namespaces has a weakly layered architecture, so that namespaces in that group can use transitively reachable namespaces;
  • go, with the team, through the list of dependency rules;
  • analyze with the team the picture with the transitive edges;
or, preferably,
  • all three!

Last edited Jun 21, 2010 at 8:22 PM by thoemmi, version 7

Comments

No comments yet.