Volatility: Understanding Dependencies is Key

In the previous post, we discussed forces that can cause an application to change. Robert C. Martin, in his white paper on Stability, outlined three areas that cause volatility. Failure to correctly account for volatility in your design process will yield a product that cannot embrace change. But why is this so?

Before we can dive further into the realm of volatility, you must have a firm understanding of dependencies.

It’s All About Dependencies

Take the following diagram, as an example:

Simple Dependencies

Figure 1: Simple Dependency Graph

Consider that package A has a dependency on package B. Package B has a dependency on package C. Because A has no upstream dependencies, it can be easily changed without negatively affecting any other component.

Package A, however, depends upon package B. Any changes in B may adversely affect package A. Package C is in the worst situation, as changes in it will directly affect package B and indirectly affect package A. To say it a different way, you, as the developer, must consider every usage of C either by B or via A before you can make a change to C.

This is a simple truth. Package A is easier to change than B and B easier than C. Because C is the hardest to change, it is considered, by definition, to be the most stable. Because A is the easiest to change, it is considered to be the least stable.

This phenomena is documented by Robert as the Stable Dependencies Principle (SDP):

The dependencies between packages in a design should be in the direction of the stability of the packages. A package should only depend upon packages that are more stable that it is.

It is important to recognize here that Robert’s definition of the word stability is derived from Websters Third New International Dictionary – “not easily moved.” It does not imply reliability in any sense of the word. A package may be stable by dependency, but unreliable in quality. In contrast, a package may be instable by dependency, but consistently reliable.

Monolithic Applications Don’t Work

The SDP is the entire reason why monolithic applications don’t scale. Consider the following:

Monolithic Dependencies

Figure 2: Monolithic Dependency Graph

The above diagram outlines a very simple application that has a Program class with a main method that bootstraps a main form. All of the business logic is in the main form. When a button is clicked, the Foo() method is invoked, which in turn invokes Bar().

In this example, the entire application is, by definition, instable. Nothing depends upon the application and thus, from a dependency perspective, is totally expendable. Both the Program class and the main form reside in this expendable application.

The entry point could potentially be a dependency of the Main() method in the Program class. However, I consider this to be a weak dependency. The most stable class is therefore the main form, as the program class depends upon it. In turn, the Bar() method is the most stable method in the application.

We are supposed to design applications that can handle new features, new requirements, and new use cases. If both Foo() and Bar() are the guts of the application, then we have a serious issue. The business logic would be in the worst possible place. These are the two hardest methods to change in the entire application. They are the most stable methods in the most stable class.

Let me say it a different way. Changing Bar() both directly and indirectly impacts the entire application. There is no way to extract the business logic of the application without performing radical surgery on the main form. So what do we do about this?

A Layered Approach

The only way out of this mess is to enforce some rules regarding communication in the system. You must dictate which components are allowed to talk to each other and when. A common way to accomplish this is to divide your application up into various layers.

If Figure 1 represented the layers of the application, then package A would represent a presentation layer. Packages B and C could represent other layers in the system, such as business logic and data access. You can choose from a myriad of different layered architectural patterns: N-Layer, Hexagonal, Onion, the IDesign Method, and the like.

Many software architects have strong opinions why one architectural layer pattern is better than another. I’ve seen this personally in the DDD community. I’m here to tell you, though, that many architects are doing it incorrectly.

Stay tuned. We’re just getting warmed up.

Leave a Reply