Using metrics for crafting a better software architecture — the Java and Maven approach

Ignatij Gichevski
Javarevisited
Published in
7 min readDec 27, 2021

--

Have you ever found yourself in a situation where changing a simple line of code results in a bunch of compilation errors across the whole application?
Or introducing a bug some place you would at least expect?
Well if you haven’t been there, I have, and let me tell you it’s not pleasant.
Not. At. All.

But the question that we as a team asked is how did we manage to get here?Looking back retrospectively, we followed some common guidelines, had a good test coverage, made sure to establish some good practices beforehand and yet we managed to get to a place where changing one line turns out to be a full three-day effort.

Good luck explaining that to your manager!

Let me start from scratch. You’re starting on a new project that starts simple, but things quickly start to escalate and before you know it, you have to establish some ground rules.
You probably have some business logic, so you create a module that will wrap that logic, let’s say it core module. You will probably also need an API, so you will also add an API module. You need security, so you add a security module. Not to mention if you’re working with DDD or having a CQRS or Hexagonal architecture, then you will probably end up with a bunch of different modules covering who knows what at the end.

Don’t get me wrong, that’s probably the right way to go if you want to have a maintainable and scalable solution on the long run. It’s definitely a good approach to separate the various concerns and group the common ones.

So, what did we do wrong to end up where we did?

Well, it turns out that it’s not only important to just create different modules and separate the concerns, but also how you connect those modules, how you introduce the dependencies between them.

Robert C.Martin mentioned this problem in his Clean Architecture book and he proposed one solution to it.
What he proposed is introducing some kind of metrics based entirely on how the modules of a system are connected between themselves. The purpose of those metrics is to be able to determine how scalable and maintainable a system is.

In the following part of this article, before diving into a more practical work, I’ll try to do a very brief summary of the theory behind the metrics that Robert Martin explained in much more details in the chapter of his book (yes, obviously I highly recommend reading that one over the following two-minute long TL;DR part).

Dependency Management Metrics

The Dependency Management metrics are consisted from two principles:

  • Stable Dependencies principle
  • Stable Abstractions principle

In the part that follows, we will refer to the term component which is practically the equivalent of a Maven module in the Java and Maven ecosystem.

Stable Dependencies principle

“Any component that we expect to be volatile, should not be depended on by a component that’s difficult to change. Otherwise, the volatile component will also be difficult to change.”

What actually makes a component difficult to change (a.k.a “stable”)?
Many factors may make a software component difficult to change (size, complexity, clarity, number of components that depend on it…).
What we are particularly interested is the last case, when a component has a number of other components that depend on it. A component with lots of incoming dependencies is very hard to change, hence the name “stable” component:

As you might have guessed it already, an “unstable” component is quite the opposite.
An “unstable” component is a component which depends on other components and yet no other components depends on it:

Instability of component

We can define the term Instability of a component as follows:

Now that we have defined the Instability, we can define the actual principle of Stable Dependencies:

“A component should always be dependent of a more stable component.”

On the following image we can see a clear violation of the principle:

where Stable Component(dependent on by two components) depends on unstable component(dependent on by only one component — Stable Component).

Stable Abstractions principle

To be able to define the Stable Abstractions principle, we first need to establish the definition of the term — Abstract component.

“Abstract component is a component that holds only interfaces or abstract classes.”

These abstract components are very stable and ideal for less stable components to depend on.

We can define the level of Abstraction of a single component on the following way:

Now that we have defined the term Abstraction of a component, we can define the Stable Abstractions principle:

“A component should always be dependent of a more abstract component.”

Main Sequence

We have defined the two principles, so what’s next?
Let’s try to define a connection between the Instability and Abstractness metrics on an x-y axis:

An X-Y axis of Instability and Abstractness of a component
X-Y axis where X-axis is Instability, Y-axis is Abstractness of a component

If we draw a line between the (0,1) and (1,0) points on the x-y axis stated above, we get a line that’s called the Main Sequence line:

This line represents a place where all components should be. A component that sits on the Main Sequence is neither too abstract, nor is too unstable.

Now that we have defined where the components should be, it’s time to define where exactly they shouldn’t be, in the so-called Zones of Exclusions:

Zones of Exclusions

As we can see there are two zones where we don’t want our components to be:

Zone of Pain
Highly stable and concrete components. This is not desirable because the components that are in this region are very rigid. They cannot be extended because they lack abstractness and they are very difficult to change due to their high stability.

Zone of Uselessness
Highly abstract components, without any dependents, i.e. useless components.

Distance from Main Sequence
Now it’s time to define the metric that corresponds to the distance of how far away is a component from the Main Sequence:

Any component that has a D metric value that’s not near zero, can be further reexamined and restructured.

Statistical analysis of a design is also possible by calculating the mean and variance of all the D metrics for the components.
A conforming design should have mean and variance values that are close to zero.

Dependency Management Metrics Maven Plugin

Hopefully you got the idea from the theoretical part, now it’s time to see how we could actually benefit from these metrics on a more practical level for projects that use Java and Maven.

Dependency Management Metrics is a Maven plugin that calculates and outputs the Dependency Management metrics for each Maven module within a Java multi-module project. The plugin takes into consideration only dependencies which are internal and project-specific, not external dependencies (i.e. Spring).

The following metrics are taken into consideration for each Maven module:

  • Stability metric
  • Abstraction metric
  • Distance from Main Sequence

The plugin also outputs the modules present in the Zone of Pain and the Zone of Uselessness in a separate section, Zones of Exclusions section.

And finally, the plugin can potentially break the Maven build if either the Stable Dependencies or Stable Abstractions principle is violated.

Results

Running the plugin in a multi-module Java project produces the following output:

From the output we can see that some of the modules which are misplaced the most belong in the Zone Of Pain section, meaning that they are highly stable modules that clearly lack abstractions.

What does this mean for us?
This practically means that changes in those modules should be as small and as rarely as possible, so we don’t unexpectedly introduce some bugs, build problems or simply increase the headache factor among the developers (and the managers, some time later).

Conclusion

The plugin itself doesn’t fix the problem for us, its purpose is to point out the spots where we can further act and improve.

It can act both as a general overview of the current architecture and more importantly, as a guideline for future planned work — be able to recognise if some refactoring or restructuring is needed beforehand, which hopefully on the long run it will contribute to a better, much scalable and maintainable software.

--

--