The subtle art of decoupling systems

Systems coupling has several dimensions. Hence, decoupling each aspect can take you in a different direction with conflicting trade-offs.

Coupling is a measure of how closely connected two functions, modules or applications are. Loose coupling is a good software design practice, since it allows you to lower cost of maintenance, segregate performance issues, reduce the risk that a component failure will propagate into another, increases the readability of code, among others.

In this article, we will review the different dimensions of coupling, the ways in which they can be decoupled and benefits. It is not a matter of 0s and 1s; either coupled or not. There are different degrees of coupling.

Coupling dimensions

Not all types of coupling are made equal. There are several different ways in which 2 systems can be coupled. Obviously, we want to communicate between 2 systems, then they will be coupled in some way. Let’s imagine system X and system Y. Whenever it happens that if we change system X and system Y needs to change or is impacted somehow, then they are coupled in some way.

Different ways of coupling systems

Gregor authored a worth reading article [3], in which he mentions the different facets of coupling:

Technology coupling

Let’s say you have 2 apps written in Java, they exchange messages and everything is great. If we introduce a third app, written in Node.js and we want to integrate it with one of the previous ones. However, changes are required due to the language differences, then clearly there is a technology coupling. Usually, we would use message payloads in a language agnostic like XML or JSON to avoid coupling.

Location coupling

If all systems connecting to a given app are assuming the app is running on the same network, may be using a hard-coded IP. Then, this is a case of location coupling.

Data format coupling

A data format coupling happens when both systems connected are expecting a certain format. Canonical data models [5] can provide an additional level of independence in the integration that decouples formats. This is particularly useful when having several applications integrated.

Data type coupling

The classic case is date types. The ideal is to adhere to a given standard so all systems agree upfront on what to expect. An ISO 8601 Date is a string format which can hold date, time, milliseconds and timezone, and is human readable.

Temporal coupling (run-time coupling)

This is one of the most important aspects. Is it synchronous or asynchronous? Sync imposes a temporal coupling, in the sense that both systems will need to be up and running to exchange messages. Meanwhile, async offers temporal decoupling as there will be a component ‘holding’ messages to be delivered later, e.g. messaging queues.

Design time coupling

When a system is changing (e.g., adding/modifying features), and another needs to change as well due to the first system’s change. Then, design time coupling is happening.

Decoupling options and benefits

Decoupling is defined as reducing direct dependence between two or more components, services or systems. The follow up question is why, in other words, what are the benefits of decoupling components, services or systems?

Code maintainability

When following the Single Responsibility Principle (SRP), each component should have a single responsibility:

There should never be more than one reason for a component to change.”

Having a single purpose, makes the code maintainable, reducing operational costs and also increasing the reusability. This is a great strategy to address design time coupling.

Modularity

One of the strategies to tackle design time coupling is to use modular architectures [4]. Modularity is the encapsulation of functionality and complexity hiding behind interfaces. It allows components to be added, removed, or modified independently. Modularity helps in isolating changes and minimizing the impact on other parts of the system during scaling.

Performance efficiency

Once components are decoupled, it is a lot easier to optimize performance individually and as a whole. Furthermore, allows different services based on those components to scale horizontally. [1]

The other aspect is run-time or temporal coupling, whenever we decouple in time (e.g., async calls), we have a performance trade-off. On one hand, it will usually improve performance if calls are synchronous, however, on the other it has clear disadvantages.

Fault tolerance

When a system component has reduced dependencies with others, facilitates the failures handling, hence improving fault tolerance capabilities of your system as a whole. The cloud by itself will not guarantee that your solution will be fault tolerant. You can leverage services that are fault tolerant, however, it is the system architect / designer’s work to meet the agreed SLAs/performance using the required components, services, frameworks, techniques, etc. [2]

Data consistency

Standardized data format across applications and the whole Enterprise brings consistency which is highly beneficial. Finding the right level of coupling for data formats and types requires time and dedication, however it bring enormous benefits.

References

  • [1] Comprehensive software qualities for scalability – PentaTech
  • [2] Does cloud guarantee fault tolerance? by Pablo Iorio
  • [3] I made everything loosely coupled. Will my cloud bill go up? by Gregor Hohpe
  • [4] Linking Modular Architecture to Development Teams by Matthew Foster
  • [5] Canonical Data Model by Gregor Hohpe