Dependencies are at the heart of software architecture. Getting your dependencies right is the basis of keeping your software maintainable and evolvable. There are “good” and “bad” dependencies. Bad dependencies will make your system hard to understand, change and test.
Types of Dependencies
A dependency between two artifacts A and B exists if A somehow “needs” B. In OOP, the most important artifacts are classes (and interfaces, which are technically just pure abstract classes). We can further differentiate the type of dependency between two given classes: A might
- inherit from (or implement) B
- accept B instances as input
- retrieve B instances
- create B instances
- access a B instance (method, constant, or field)
- return B instances
Usually multiple of these types apply. For example, A somehow needs to get a hold of a B instance in order to access or return it. So if A is not B instance itself, it could either create one, (actively) retrieve one or (passively) accept one as input. In this article, I do not distinguish between compile time and runtime dependencies, nor do I explicitly consider resource dependencies (files, local hardware, remote systems etc.).
A set of interdependent classes forms a directed graph, with the classes being the nodes and the dependencies being the edges. Each dependency has a direction, and other properties such as its type from the list above. Using this information, we can derive some quality metrics. We could count the number of incoming or outgoing dependencies, detect cycles, or consider the types of the dependencies. There are some well known metrics, probably the most well-known being the Metrics Suite for Object Oriented Design by Chidamber and Kemerer.
Awareness of the Target Architecture
The common metrics consider all classes equal. But in fact, they are not. For example:
- Interfaces and API classes are different from implementation classes
- Infrastructure classes are different from business logic classes
- GUI classes are different database access classes
What makes them different? The answer is: their relation to a target architecture. Technically, a Factory is just a regular class. But it is our design/architecture that defines that this class may instantiate classes, while other (business logic) classes may not instantiate any classes. Furthermore, considering the architecture, classes not only belong to packages, but to some higher level “thing” (call it component, module, subsystem…). Usually, we design a system with such high level abstractions in mind. And we define allowed dependencies at this level of abstraction. But our implementation language might not provide mechanisms to ensure our architectural guidelines are followed. For example, Java still does not provide a standard way of organizing our classes in modules and cleanly separating their APIs from their implementations (like OSGi does). And some other architecture principles will never be expressible in the language itself (see the factory example above).
My Vision is an architecture-aware dependency analysis that enables developers and architects to spot problems before it is too late to fix them. The analysis will classify dependencies as “good” (valid) or “bad” (invalid), just like Unit Tests that either pass or fail. Developers will be reminded that they violated the target architecture if they imported a class from a wrong package or tried to instantiate a class in business logic code.
What do we need? First, we need to make our target architecture explicit using a model. We then need to map our concrete software artifacts (classes, packages etc.) to the elements of our architecture model. This may be simplified by using some naming conventions for the implementation artifacts. Finally, we need to specify general constraints (like “classes may not create other classes” or “implementation classes of one module may not access implementation classes of other modules”) and exceptions from these constraints (like “Factory classes may create classes from their own package”). The architecture definition will need to be evolved along with the software.