A few posts back I talked about how we can use the assembly references to enforce consistency and separation of concerns (here and here are the old posts). I argue there that if we derive from the architecture the assemblies of our application, the kind of code (responsibility) each one holds and how they can reference each other, then monitoring this can become a useful tool to assure that the critical architectural aspects are followed by the implementation.
In this post I will show how am I verifying the assembly references and the tool that I use. It is usually the first thing I do when I start a review on a new project. I find it an efficient way to spot code design smells, bad questionable design decisions or implementation “shortcuts”, which may hurt really badly the project on the long run.
Let’s dive into details. My favorite tool for this is ReSharper. More exactly the Project Dependency Diagram. It can be generated from the ReSharper | Architecure menu. What I like the most about it is that in ReSharper 9, it is augmented with the Type Dependency Diagram, and now you can easily drill down in any reference to see the dependencies among the classes, and even further to the lines of code that make that reference needed. Now, when you see a reference that shouldn’t be there you can easily find the code that created it and reason about it. (In ReSharper 8, I was using the Optimize Refernces view to drill down to the lines of code that made a reference needed.)
I’m not going to explain in detail how the tool works, you can read about on the ReSharper blog. Let’s look at an example instead.
Here I have generated the reference diagram for a demo project from my Code Design Training (code available on GitHub here). A few things to notice as architectural principles to follow for this project:
- The application has more UI clients (a WPF and Console for now). They cannot have dependencies (references) one on the other, because we want them independent.
- The application consists of more modules. Each module has its own assemblies and they cannot have dependencies (references) among each other. The modules interact only through the Contracts assembly which has only pure interfaces (service contracts) and DTOs (data contracts).
- This is applying the DIP (see here) and makes that the implementation of the modules is encapsulated and abstracted through the types in the Contracts assembly.
- The UI gest the functionality implemented by the modules only through the contracts. The UI cannot have direct references to the implementation of the modules, neither to the Data Access. This enforces that the application logic does not depend on the UI, but the other way around (again applying DIP).
Any new reference, which does not obey the above architectural principles will easily be found when we re-generate the diagram from code.
If we go in more details we see other development patterns.
Each module has a .Services assembly which implements or consumes Contracts. The module assemblies may reference and use the DataAccess or the Common assembly from the Infrastructure. These are not necessarily rules as strict as the above, but more like conventions which create a consistency on how a module is structured. The reference diagram can help a lot to see how these evolve.
Look again to the diagrams. Which reference do you think is strange and might be wrong? The Sales module references the DataAccess. This is fine. It needs to use the IRepository / IUnitOfWork to access data. But, one of the Sales module assemblies is referenced back by the DataAccess. This is wrong. We would want that when the implementations of any of the modules changes, the Infrastructure assemblies not to be affected, because if they are then their change may trigger changes into the other modules as well. So, we’ll have a wave of changes which starts from one module and propagates to other modules. If you look to the first diagram, this reference looks like it creates a circular dependency which may be a better smell that something is wrong. If we right-click the reference we can open the Type Dependency Diagram.
Here we see that the SalesEntities, from the DataAccess, is the class that created this reference. If I hold the cursor on the dependency arrow I get all the classes it depends on. This class is the EntityFramework DbContext for the Sales module. It should not be here, but the DataAccess needed it to new it up. (in fact this is a TODO that I have postponed for a while in my demo project). To fix this ‘wrong’ reference we have to invert the dependency. So we should create an abstraction. We can make a IDbContextFactory interface into the DataAccess, move the SalesEntities into one of the Sales module assemblies and implement there the factory interface to new it up.
This is a good example of how this tool can help us to find code at wrong level of abstraction, by spotting wrong dependencies. SalesEntities is a high level concept class. It describes the domain of the Sales module. It was wrongly placed into a lower level mechanism implementation, that implements data access.
If you can spread this code review practice among your team more benefits will follow. Each time a new reference or a new assembly appears the team will challenge if it fits into the rules and into the reasoning behind those rules. It will get you towards contextual consistency: this is how we build and structure things in this project. It may not apply to other contexts, but it makes sense in ours and we know why. Consistency is the primary mechanism for simplicity at scale. Reviewing dependencies easily and fast, shared idioms and guiding principles help create and sustain consistency. Once you have consistency, difference is data! There has to be a good reason why things that break consistency in a particular spot are tolerated.
To conclude, the tool that we use to generate the references diagram is not the most important thing here. I like ReSharper, but you can get almost the same with the architecture tools from Visual Studio Enterprise / Ultimate. What is important is to use a tool that can generate useful dependencies diagrams from the code and to constantly monitor them. The entire team should be doing this. By reviewing these dependencies regularly you make sure that the critical architectural principles and requirements are still met.