Where you might want to start reading ...

Is there something wrong with software architecture - or with us?

I am a software architect (one of a few) for a 20-million LOC business software, with currently a few thousand installations, developed and ...

Sunday, June 18, 2017

Broad or narrow-spectrum prescriptions - that's the question

In my last posting, I have shown that writing precise rules for dependencies requires formulating a standard set of rules that allow using all the self-evident parts of the chosen runtime environment.

However, after we did that for the Archichect tool itself, there were still more than six thousand dependencies that violate the first, simplistic set of rules that I derived from a rough overview. What are the reasons for these violations? Essentially, there are two:
Here, I will concentrate on the first item, and derive an interesting and important question from it. The next posting will then deal with the second topic.

Before I start with the concrete rules, I would like to know how many of the present violations are of the first kind—i.e., referring external classes—, and how many of the second. For this, I simply add a rule
Archichect.** ---? Archichect.**
This rule allows every item inside Archichect to use any other Archichect item. Of course, I must take care to remove this rule from the final rule set afterwards. That's the reason why I chose the "question mark arrow", which will mark all matching dependencies as "questionable"—hopefully, I will remember to remove this rule later (an additional comment might also be helpful ...).

With this rule added, Archichect's output is Writing 1438 violations and 4717 questionable dependencies. Thus, we will deal in this step with around 1400 dependencies—the rest will be tackled in the next posting.

Now, what about these "violations"? Among other things, Archichect can do the following (I will explain the reasons for these features later, maybe much later—right now, assume that these features are just a "given"):
  1. It can extract dependencies from .Net assemblies (".DLLs" and ".EXEs");
  2. it can watch files (e.g. rule files or source files) and automatically rerun a check in a new thread when a file changes;
  3. it can create some architectural diagrams;
  4. of course, it accesses files and directories at many places;
  5. it provides a small web server for accessing output via a browser;
  6. it can write rule violations in an XML format (but right now not JSON, which is an unforgivable oversight; I will repair this soon);
  7. it can load plugins from assemblies for reading, transforming, writing, and more;
  8. it caches items and strings internally for more efficient memory usage.
These functionalities are located in the following classes:
  1. Reading .Net assemblies: Classes in namespace Archichect.Reading.AssemblyReading;
  2. file watching: Class FileWatcher in namespace Archichect;
  3. diagram creation: Classes in namespace Archichect.Rendering.GraphicsRendering;
  4. file and directory access: many classes need this for reading (of dependencies or configurations) and writing (of violations, logs etc.); so I allow it just for all of Archichect.
  5. webserver: Classes in namespace Archichect.WebServing;
  6. XML writing: Class RuleViolationWriter in namespace Archichect.Rendering.TextWriting;
  7. loading plugins: Classes GlobalContext and ItemAndDependencyFactoryList in namespace Archichect;
  8. Caching of items: Classes in namespace Archichect and child namespaces Archichect.Reading and Archichect.Rendering; in turn, the classes implementing the cache use System.Threading.
The following rules capture the intention that these packages are allowed to use certain namespaces from external libraries:
Archichect.Reading.AssemblyReading     ---> Mono.(Cecil.**|Collections.Generic)
        // For reading .Net assemblies, Archichect uses Mono.Cecil,
        // including some general Mono collection classes
Archichect:FileWatcher             
    ---> System.ComponentModel:Component
        // .Net's FileSystemWatcher derives from Component
Archichect:FileWatcher
                ---> System.Threading
Archichect.Rendering.GraphicsRendering ---> System.Drawing.**
Archichect.**                          ---> System.IO
Archichect.WebServing                  ---> System.Net
Archichect.Rendering.TextWriting:RuleViolationWriter.** ---> System.Xml.**
Archichect(.Reading.**|.Rendering.**)? ---> Gibraltar
        // String caching I copied from the web and generalized for arbitrary objects
Gibraltar                              ---> System.Threading
And now—ta-ta-ta-taa: Running Archichect with these rules leaves us with a mere 40 violations, down from the 1438 just minutes ago! It seems I have described an interesting part of Archichect's architecture almost perfectly (but, well, I invented the tool and wrote its code, so it would be fatal if I couldn't accomplish this, wouldn't it?)

What about these last 40? A quick scan of them exposes the following two reasons:
  • A somewhat widespread use of System.Reflection.MemberInfo, where its Name property is accessed. This results from code like ...GetType().Name, which I find totally ok. On the other hand, I do not want to allow use of all of System.Reflection everywhere, therefore I add the specific rule
  • Archichect.** ---> System.Reflection:MemberInfo::get_Name
  • The GlobalContext class uses System.Threading's CancellationTokenSource and CancellationToken as well as System.Reflection's Assembly and AssemblyName classes. I could now add another two rules going from GlobalContext to ... but wait: Am I still doing architecture here, or is this going into nitty-gritty details that concern no one?
That's an interesting question: Should the prescriptive architecture be
  • "narrow", i.e., allow only access from and to items where we positively need this for current features?
  • Or should it be "broad", i.e., allow the use of large swaths of foreign libraries in most of the product so that future extensions and modifications can use them freely?
I have to confess that I have never managed to keep a "narrow regime" up for a long time: Developers feel or actually are constrained to solve the problems they are supposed to solve. It seems to me that dependency rules should mostly "forbid what has to be forbidden", i.e., prevent definitely problematic design decisions. But they should not restrict something just because "there might be a reason against it, if we only think hard enough". Also, developers will find ways to subvert rules that are not common-sense or simply obstructive: So, my advice is, don't be narrow, be broad.

Still, threads and reflection are not something to use lightly. So, I will restrict the use of these two packages to the cancellation and assembly related classes, respectively; but allow that they be used everywhere in Archichect.

Of course, in your environment, there might be hard and fast rules that must be followed no matter what—so much better so that one can define and check precise dependency rules with tools like Archichect (when and if it is completed).

However, these are still lacking for the more than 4000 open "violations" flagged inside Archichect. The next posting is a stepping stone to tackling them.

No comments:

Post a Comment