Click here to Skip to main content
15,894,410 members
Articles
(untagged)

Software Component Principles Revisited

Rate me:
Please Sign up or sign in to vote.
4.60/5 (3 votes)
26 Feb 2019CPOL7 min read 6.1K   7  
In this fast-paced industry, it is important not to forget about the well-established principles and practices that have already proven their ability to ensure high-quality software. This article revisits some important principles for managing software components.

Introduction

Most software systems are built out of smaller components. In his book, Clean Architecture, Robert C. Martin (Uncle Bob) describes a number of software component principles. Components are the units of deployments of a software system. In for example .NET, components are DLLs. Some of the most important component principles are about component coupling – the relationships between components. These principles have little to do with the functionality of the software system, but everything to do with the maintainability of the system.

Acyclic Dependencies

The most obvious principle is about avoiding cyclic dependencies. Cyclic dependencies between components lead to many problems. Such components are hard to isolate and unit test. Furthermore, it can be difficult to work out the build order, and deployment can easily become error prone.

 

Image 1

However, it is always possible to break a cyclic dependency graph and make it acyclic - for example, by inverting the direction of the dependency between two components using the Dependency Inversion Principle. Let´s say we have a generic job execution service that depends on a specific job worker in another component to do the job:

Image 2

By changing the job execution service to depend on an abstraction of a job worker in form of an IWorker interface and letting the job worker be an implementation of this interface, the dependency between the two components is conveniently turned around:

Image 3

Furthermore, we have decoupled the two components from each other, because now the job execution service can work with any implementation of the IWorker interface. Generally, the Dependency Inversion Principle is one of the most efficient ways to control the dependency graph and ensure a high degree of decoupling between the essentials and the details of a software system.

Stable Dependencies

In any software system, it is important to have a very clear distinction between the components we expect to be volatile - i.e., components that we expect to change very often - and the less volatile, more essential and stable components. Therefore, the component dependency graph must be designed so that the stable high-value components are protected from components that are more volatile. For example, we don't want cosmetic changes in the UI to have an impact of the high-level policies or business functionality of the system.

One thing that definitely makes a software component hard to change is if there are many other components depending on it. Such a component is inherently stable. If it also has zero, or very few dependencies itself, it is independent and easy to isolate and unit test.

Image 4

If a component is stable, it is hard to change. If it is hard to change, it should preferably be easy to extend. This is known as the Stable Abstractions Principle and this principle goes hand in hand with the Open/Closed Principle, which states that such components should be open for extension but closed for modification.

Conversely, a component that has many dependencies and zero, or very few dependents is relatively easy to change. Such a component is instable. All software systems need some instable components that can be easily modified - or even exchanged for other components.

Image 5

The Stability-Extensibility Graph

Having established the terms of stability and extensibility, we can illustrate the relationship between these two terms in a graph.

Image 6

Zone of Uselessness

Components in the Zone of Uselessness (with high extensibility and little stability) are very abstract and extensible – yet with very few dependents. Such components are typically over-engineered abstract classes or interfaces that no one ever implemented. They are practi­cally useless.

Zone of Pain

Components in the Zone of Pain (with little extensibility and high stability) are generally not desirable because they are rigid and hard to change. For example, database schemas fall in this category. They are very concrete (with no abstraction) and highly depended upon by other components. Furthermore, a database schema is very volatile. The more volatile a component in the Zone of Pain is, the more “painful” it is. However, non-volatile components are more or less harmless in this zone, because they are not supposed to change. Think of, for example, the mscorlib.dll of the .NET Framework.

Main Sequence Band

Preferably, most of the components should be within the zone that Uncle Bob calls the Main Sequence Band – borrowing a term from astronomy. The most desirable position of a component is at one of the two ends of the Main Sequence Band.

In the upper right corner, we have components with a lot of generic types, interfaces and abstract classes. They are highly depended upon, and therefore very stable and hard to modify, but they are easily extended due to their high level of abstraction.

In the lower left corner, we have very volatile components such as UI- and reporting components, plugins/adapters, device drivers, etc. with a high dependency on other components - even on third party components from various frameworks and technologies. Such components are meant to be volatile, easy to modify and even exchangeable with other components.

The Mapping Exercise

So much for Uncle Bob’s component principles. As an exercise, I have tried to map the software components from another CodeProject article of mine to the Stability-Extensibility graph. This is a very simple code base with only 3 components (excluding the unit tests) – but nevertheless a very good example, as these components have some very distinct characteristics when it comes to extensibility and stability. The 3 components and their dependencies conceptually look like this:

Image 7

The metric for the stability is the number of incoming dependencies in relation to the total number of incoming and outgoing dependencies for a component.

The metric for the extensibility is the number of generic types, abstract types and interfaces in relation to the total number of types in a component.

The result of this mapping is shown in the below figure:

Image 8

In the upper right corner, we have the DomainServices component. This component only contains generic, abstract types and interfaces, for example the abstract BaseService type and the IRepository interface. Thus, it is highly extensible. This component is a generic library/framework for various service components and will therefore have a lot of dependents, making it a very stable component.

One such dependent is the MyServices component. For example, the Products type is inherited from the BaseService type and the IProductRepository is an extension or the IRepository interface – both abstractions defined in the DomainServices component. Even if less generic and extensible than the DomainServices component, MyServices is still somewhat extensible – for example, through the IProductRepository interface. MyServices also has its own dependencies – for example, data access components implementing the IProductRepository interface and potentially UI components consuming the Products service. These characteristics (somewhat extensible and semi-stable) place the MyServices component right in the middle of the graph.

In the lower left corner, we have the data access component MyServices.Data. This component comprises the concrete technology-specific implementations of the MyServices repository interfaces, for example, the JsonProductRepository that is an implementation of the IProductRepository interface. Because it only contains concrete types, this component is inextensible. MyServices.Data has a dependency on MyServices and the third party Json.NET library but has no dependents itself, making it as instable as it gets.

The graph clearly illustrates that there is a very distinct separation between the stable high-level policies and abstractions of the DomainServices component, the business functionality of the MyServices component and the highly volatile data access component MyServices.Data. And luckily, no components end up in the Zone of Pain or the Zone of Uselessness…

Summary

Almost any software system is built out of smaller building blocks called components. Following well-known best practices and principles when creating software components is one of the most important aspects of solid software design and architecture.

It is important to have a very clear distinction between the components that we expect to be volatile - i.e., components that we expect to change very often - and the less volatile, more essential components.

Being in full control of the relationships between components, and especially mastering the Dependency Inversion Principle to ensure a high degree of decoupling between the essentials and the details of a software system, will dramatically increase the maintainability of the system.

The two main characteristics of a component is the extensibility and the stability of the component. The “perfect” component is either highly extensible and stable, hence difficult to modify, but easily extended or inextensible and instable, hence uncomplicated to modify or even exchange with other components.

It is a useful exercise to map the components of a software system into the Stability-Extensibility graph to ensure that the majority of components are located in the Main Sequence Band.

History

  • 24th February, 2019: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Denmark Denmark
I am a software architect/developer/programmer.

I have a rather pragmatic approach towards programming, but I have realized that it takes a lot of discipline to be agile. I try to practice good craftsmanship and making it work.

Comments and Discussions

 
-- There are no messages in this forum --