Published February 9, 2022
by Doug Klugh
Designing Dependencies
Design components by employing Dependency Management principles to establish a stable component architecture with components that can be developed and deployed independently of each other. These principles provide guidance on how to organize components and create dependencies that best promote flexibility, while isolating the effects of change.
Design your software in such a way that the dependencies between your components do not form a cycle. The resulting component dependency structure will form a Directed Acyclic Graph — such as a tree structure. Draw your component graph as a tree and make sure all the arrows point downward. Then rest assured that your dependencies do not form a cycle.
Design your software in such a way that any given component depends on other components that are more stable than it is. In other words, the dependencies within a component architecture should be pointing in the direction of increasing stability. This will ensure that components that need to change frequently will be easy to change and that the stable components that others depend on will not change nearly as often.
Design your software in such a way that components are as abstract as they are stable. This supports the Stable Dependencies Principle and promotes flexibility by ensuring that stable components are easy to extend, even though they are hard to change. This is achieved by applying the Open/Closed Principle, which enables us to easily extend functionality without modifying existing code. This approach also works best when adhering to the Dependency Inversion Principle, which states that dependencies should point towards abstractions and not concretions. This provides very effective dependency management for software components.
So how do we compare the stability of components or know which component is more stable than others? The following component metrics will help to determine just that.
First we need to count the number of incoming and outgoing dependencies for each component. These numbers are called Afferent and Efferent Couplings.
The number of classes outside of a component that depend on classes inside that component (incoming dependencies).
The number of classes outside of a component that classes within that component depend upon (outgoing dependencies).
Next we need to calculate the instability ratio for each component.
This Ri metric will range from zero (0) to one (1). Components with an Ri value of 0 are completely stable — they have no dependencies. Components with an Ri value of 1 are completely unstable with many dependencies. All other Ri values provide a scale from very stable to very unstable. As it turns out, Ri is a pretty good numerical indicator of a component's resilience to change and we can use this number to compare the flexibility of components.
Then we calculate the abstractness ratio for each component.
If your components are well structured, Ra + Ri should equal 1 (or very close to it). This is the Stable Abstractions Principle and this metric serves as a numerical indicator of how well your components adhere to the Dependency Inversion Principle. And as you (should) know, dependencies should always point towards abstractions and not towards concretions.
So what if Ra + Ri ≠ 1? Then you either have a lot of components depending on concrete classes (which is bad) or a lot of abstract classes with no incoming dependencies (which is also bad). Either way, it's a violation of the Dependency Inversion Principle and should serve as a giant red flag. Can you say "technical debt"?
Figure 1 below shows a diagram that plots abstractness and instability, with abstractness on the Y-axis and instability on the X-axis. The top-left corner represents nothing but abstract classes with all dependencies coming in. The bottom-right corner represents nothing but concrete classes with all dependencies going out.
The other two corners represent problem areas to be avoided. The top-right corner is the Zone of Uselessness as that represents abstract classes with nothing depending on them and nothing implementing them (i.e. useless).
The bottom-left corner is the Zone of Pain as that represents concrete components with lots on incoming dependencies. That's not so bad if those components never change, but if they do, Mr. T has a prediction for you: Pain! I pity the fool who keeps components that change in that bottom-left corner.
Our goal is to keep our components as close to the main sequence line as we can. Bob Martin chose to call those points where Ra + Ri = 1 the Main Sequence (not be to confused with the main sequence of stars). That's where we want our components to lie — right on the main sequence.
For components that don't lie on the main sequence, we can calculate their distance from the main sequence using this formula:
This Distance metric will range from zero (0) to one (1), where 0 is right on the main sequence and 1 is as far from the main sequence as you can get.
You can track this Distance metric for all your components and give attention to those that start creeping towards a value of 1. If you really want to get into this, you can watch for statistical deviations — looking for values that are x-sigma from the main sequence.
It may be overkill, but if you want a deterministic evaluation of how well your system complies with the Dependency Inversion Principle, how adaptable your software really is, this would probably serve you well.
A component is a collection of classes encapsulated within an independently deployable library, such as a jar, gem, or DLL. These libraries are independently deployable when a change to one does not cause others to be recompiled or redeployed. The key to developing independently deployable components is to manage dependencies. For example, changes to an application do not require that framework components be recompiled or redeployed. Applications depend on frameworks. Frameworks do not depend on applications. If developed correctly, you should be able to swap out components during a normal business day, with all users hammering away at your system, with zero downtime.
« Classes are a necessary but insufficient vehicle for decomposition. »
- Grady Booch
Imagine building a large software system out of many small and simple classes. If you only follow the Single Responsibility Principle, this is exactly what you will end up with. So, we need a way to group classes together into larger modules. Components provide a way to encapsulate related classes into larger chunks. The trick is determining which classes should be grouped together within a component — which is resolved by the Principles of Component Cohesion. Then the next challenge is managing the dependencies between components — which is solved by the Principles of Component Coupling.
« It's like building a sandcastle from individual grains of sand. »
- Bob Martin