Building web in 2024

“Balancing Coupling in Software Design” (book review)

“Balancing Coupling in Software Design”, the new book by Vlad Khononov, can be summarised by one logical expression:

BALANCE = (STRENGTH XOR DISTANCE) OR NOT VOLATILITY

If that seems a bit cryptic, don’t worry. Throughout this article, you’ll have a chance to play with each variable from the expression and get a sneak peek of the Balanced Coupling model. Inspired by Vlad’s approach to balancing theory and practice in his book, I’ve added little quizzes to test your software design intuition. If you’ve already read the book, use them to check yourself!

Let’s get started with the first variable, or shall we say, dimension of coupling.

Table Of Contents

Integration Strength

At the core of the book is the new model for evaluating the strength of coupling. The stronger the coupling, the more knowledge is shared between modules.

Try to order Integration Strength levels from strongest to weakest:

To come up with these levels, the author analysed two existing models from 1970s (Structured Design) and 1990s (Connascence) and found that not only are they outdated, but they also have significant blind spots. To help us connect with the history, this part of the book includes code samples in Assembly, PL/I, and Fortran.

Integration Strength inherits the best parts of both mentioned models, modernises them, and addresses some of the gaps.

Let’s see if you can match situations on the right with their corresponding Integration Strength levels on the left:

Let’s see how much knowledge is shared between modules in the scenarios above.

Intrusive coupling

Instead of communicating through public interfaces, the downstream module communicates through, and thus depends on, the implementation details of the upstream module

The strongest level of coupling is all about hacking around to get the job done in ways never intended for integration. Patching an external library requires understanding undocumented internals and keeping the patched behaviour in mind when applying updates. It’s worth mentioning that sometimes it’s the only way to go.

Functional coupling

Functionally coupled modules share the knowledge of the functionalities they are implementing.

From a business perspective, checkout form validation is a single piece of work that must be completed before an order is placed.

On the technical side, however, a subset of this validation begins on frontend to provide a better user experience with fast visual feedback. If frontend checks pass, it’s time to run full validation on backend. Every time the form changes, both frontend and backend have to stay in sync regarding their validation rules. They are functionally coupled.

Model coupling

Model coupling takes place when the same model of the business domain is used by multiple modules.

If the previous levels share knowledge about behaviour, model coupling only shares knowledge about internal data structures. Typically, they are easier to reason about and change less frequently. In the example above, we used PostgREST to automatically expose a database table as REST API — handy! However, every time we rename or delete columns in our table, our API clients fail.

Contract coupling

Modules are contract coupled if they communicate through an integration-specific model

As Vlad puts it, a contract is “a model of a model”, a new level of abstraction that reduces amount of shared knowledge to the bare minimum. This makes contract coupling the weakest level of the Integration Strength model.

In our example above, OAuth 2.0 is a standard protocol for authorization. If you’ve ever worked with it, you may argue that it requires a great deal of knowledge and patience to integrate with it properly.

I intentionally chose this example to emphasise that contract coupling almost always involves overhead. Let’s revisit the PostgREST situation. To “upgrade” it to the contract level, we could introduce a database view that would serve as an API contract. This would allow us to evolve the original table structure (internal model) without breaking the API, but we would need to maintain the introduced view.

Now that we can reason about the first variable in the Balanced Coupling equation, let’s move on to the second one:

BALANCE = (STRENGTH XOR DISTANCE) OR NOT VOLATILITY

Distance

Again, let’s start with a quiz. Sort the following encapsulation boundaries from the least to the most distant:

In addition to the levels listed above, there are hidden levels that we humans create when working in groups.

The greater the ownership distance, the higher the coordination effort needed to implement changes affecting multiple modules. Consequently, the perceived distance between the modules also increases accordingly.

For example, full stack web developers often reduce the distance between frontend and backend by using monorepos, sharing types, or even pieces of code between the two services. Sometimes this works out great, while other times it turns into a nightmare.

Returning to the Balance equation, it has something to do with STRENGTH XOR DISTANCE. Time to look deeper!

You may have noticed that the expression uses boolean values. This is good enough for grasping the main trends. The book also introduces a hypothetical numeric scale to illustrate the nuances.

Map boolean Strength and Distance values to the corresponding situations on the left:

Let’s walk through all the combinations of STRENGTH and DISTANCE to see what we find. Click on the blurred items below to reveal the answers.

Low distanceHigh distance
Low strength
High strength

Red items above reflect complexity while green ones contribute to modularity of the system. Apparently,

STRENGTH XOR DISTANCE = MODULARITY

Of course, the book goes deeper into the topics of modularity and abstractions, referencing Dijkstra, David Parnas, and John Ousterhout! It does the same with the topic of complexity by looking at phenomena from various angles including the Cynefin framework.

For now, let’s move on to the final variable in the equation:

Volatility - coupling in the dimension of time

Imagine a strongly coupled system. One, in which all components share excessive and extraneous knowledge across their boundaries. Even intrusive coupling is there. […] But if the components will never change, does the potential for cascading changes even matter?

Volatility reflects the frequency with which a software module changes. But how can we predict the frequency of changes without knowing future requirements? Vlad, who also happens to be the author of one of the best books on Domain-Driven Design, suggests deriving volatility from the type of DDD subdomain. The new book also includes a quick recap of DDD subdomain types if you need a refresher.

Now, try to guess expected volatility for each subdomain type:

SubdomainCompetitive AdvantageComplexityVolatility
CoreHighHigh
GenericLowHigh
SupportingLowLow

It’s worth noting that as the business evolves, new subdomains may emerge, and existing ones may change in type. In addition, volatility can be influenced by team communication patterns, reorgs, and growth.

Balancing and re-balancing

Back to the Balance equation, it’s time to put all the variables together:

BALANCE = (STRENGTH XOR DISTANCE) OR NOT VOLATILITY

We’ve already discovered that the first part of the equation means modularity, the opposite of complexity:

BALANCE = MODULARITY OR NOT VOLATILITY
BALANCE = NOT COMPLEXITY OR NOT VOLATILITY

There is one more way to express it. Give it a try:

Volatility makes modularity optional. It’s okay to have a big ball of mud as long as it rarely changes. In other cases, we can tune strength and distance to achieve modularity. Examples of balancing (and rebalancing) are my favourite part of the book!

Since in this article I want to share my understanding of the book, I’ve prepared my own example.

Example: abandoned component library

The team is working on a new frontend for a project and realises that there will, in fact, be two different frontend applications sharing the same backend and visual language. To address this requirement the team identifies the following parts, each in its own repository:

  1. Backend at backend.acme.com
  2. UI component library at styleguide.acme.com
  3. Frontend 1 at shop.acme.com
  4. Frontend 2 at journal.acme.com

In theory, everything looks great. Each part is independently deployable, and the same visual language is promoted by the centralised component library.

However, the component library soon started to lag behind the actual look and feel of the application. In one instance, the design team was late for the demo and delivered a large piece of visuals without identifying atomic components. As a result, the developed UI components were not optimised for reuse.

In another instance, a tiny visual glitch was fixed directly in the application instead of being promoted to the component library.

Slowly, more and more visual work was done directly in applications code. The product team began raising concerns about visual inconsistencies and duplicated work between the two frontend applications. The UI library was practically abandoned. What happened?

High distance

In the initial design, all parts were equally distanced from each other by using different repositories. For example, the distance between Frontend 1 and UI component library was the same as the distance between Frontend 1 and backend.

At the same time, team structure didn’t quite reflect this, with one backend team and one frontend team (owning all frontend).

DISTANCE = High (True)

Functional coupling

In the Integration Strength dimension, every frontend feature required changes in two repositories: the UI component library and the application itself. The idea was to organically grow the UI component library as part of the applications development. That meant constant switching between the two repositories:

  1. Make a change to the UI component library
  2. Release
  3. Make a change to the application
  4. Realise the UI component has to be slightly adjusted
  5. Repeat
STRENGTH = High (True)

Volatility

While the complexity of the frontend applications wasn’t high, they had to evolve at extreme pace: all new features were first implemented as frontend experiments, and only after validating user interest were they properly built on backend.

VOLATILITY = High (True)

As you can see, all three variables are high, indicating that the system is not balanced. In fact, this state of the system is called pain, or, in other words, high maintenance effort. Time for another quiz: try to complete the following equation:

Self-regulation

Without realising it, the team “rebalanced” the system by quietly abandoning the UI component library.

Better solution

The self-regulation caused issues with product quality. To mitigate this, the team reduced the distance between the UI component library and the frontend applications by merging all frontend repositories into one monorepo. Coincidentally, this also reduced the distance between the two frontend applications. But that’s a story for another time.

The book contains dozens of detailed examples and case studies that help to fully understand the Balanced Coupling model. In a couple of them, the author admits that rebalancing doesn’t always bring value; local complexity reappears at a higher level as global complexity and vice versa. Luckily, the solution is right in front of us:

Fractal geometry is nature’s way of mitigating complexity.

Fractal geometry of software design

Early in the book, Vlad draws our attention to a rather obvious fact:

A system’s component is almost always a subsystem in its own right. It consists of its own internal components, interacting to achieve its goal. Or, as Tim Berners-Lee put it, any system is a component in another, larger system

Closer to the end, the book beautifully connects this idea with the new coupling model by introducing Fractal Modularity. Regardless of the level of abstraction we operate at, Balanced Coupling serves as a self-similarity principle at all levels.

To summarise

Before discovering the Balanced Coupling model, I had a very vague understanding of the coupling phenomena.

Once, I was asked to duplicate an algorithm for calculating premium subscription in two different microservices. It was the easiest way to get job done - copy paste 3 lines of code. The system in question was in the process of decoupling from the legacy monolith, and we were going to make it worse by duplicating business logic in two separate modules owned by two different teams. Not knowing how to convince the teams to rethink the approach, I simply gave up.

The “Balanced Coupling” book refines our engineering language, enabling more productive conversations not only about coupling, but also about complexity, modularity, maintenance, stability, cost, growth, and innovation. Now it’s our turn to have these conversations, apply the new model in practice, and see how it works.

Thanks for making it this far! I hope you enjoyed the little quizzes. Here’s the last one for today:

Resources


Profile

Hi, I’m Kate 💡

I’m a Full Stack Developer and Engineering Mentor, obsessed with regular expressions, books, and web technologies. In my work, I mix old with new, soft with hard, cats with dogs. When it’s not a disaster, it’s pure magic!

LinkedIn ~ Twitter ~ Mastodon ~ RSS

Building web in 2024