We have been told that coupling is bad, so we decouple everything and break everything apart into tiny services or functions so that each service can be changed independently. But by following this reasoning we often end up with a distributed mess, Vladik Khononov noted in his presentation at the recent DDD Europe 2020 conference in Amsterdam. Instead of fighting coupling, he proposes that we use it as a design tool, as a heuristic for improving system design.
Khononov, cloud architect at DoiT International, refers to Michael Nygard who has defined coupling as the relationship between components and these components’ degree of freedom. Khononov notes that this may seem limiting, but to make changes safe we want these limits; they are part of the design and the problem we are solving in our system. He also points out that there are relations between components in all systems, otherwise it’s not a system, just a number of unrelated independent entities. To design systems, we therefore need to design the relations and treat them as an inherent part of system design.
There are various degrees of coupling and they can be observed in three dimensions: strength, distance and volatility. The combination of these three properties defines the coupling’s overall effect on a system.
The strength of a relationship defines the impact a change in one component has on the system or another component. In his two books Reliable Software Through Composite Design and Composite/Structured Design, written in the 1970’s, Glenford J. Myers describes six types of couplings related to structured design:
- Content coupling is the strongest one and means that one module directly references another module’s implementation details. This is uncommon today, but reflection is one way of accessing what should be private code.
- Common coupling. Modules communicate through a globally accessible and unstructured memory space. This is also a rare coupling in today’s systems.
- External coupling. Modules communicate through globally accessible primitive values. Although similar to common coupling, this one has less coupling because it’s better documented.
- Control coupling. A module control execution logic in another module which means it has to be familiar with the other module’s behaviour and execution context.
- Stamp coupling. Modules communicate by exchanging data structures which means they are sensitive to changes in the structure.
- Data coupling means that modules communicate by exchanging primitive types, and only those needed for the integration. This is the lowest and most desirable type of coupling of these six.
In the object-oriented programming world, evaluating relationships between modules is even more important. Connascence is a software quality metric invented by Meilir Page-Jones for reasoning about dependency relationships in object-oriented design, much like coupling did for structured design. Components are connnascent if a change in one component requires a change in another component, and if it’s possible to postulate a change that would require both components to change. As in composite design, there a different levels of connascence and they are categorized in two types: static and dynamic. Static connascence can be analysed by reading the code. In dynamic connascence, the running behaviour of the software must be observed.
Distance is another dimension of coupling. Connections between components can span different methods, classes, components, services and even different systems. The longer the distance, the more effort must be invested in order to coordinate a change that affect both components. Changing two methods belonging to the same class is much easier and cheaper than coordinating an API change for a system with numerous consumers. To minimize the coupling, Meilir Page-Jones' recommendation is to first of all refactor to the lowest level possible level of connascence. Then minimize connascence that passes encapsulation boundaries and maximize the connascence within encapsulation boundaries.
Volatility is the third dimension of coupling and it’s affected by how often components change, the reasons for change and if they have a shared reason for a change. Michael Nygard has identified five level of couplings correlated to different reasons for change: semantic, functional, development, operational and accidental. An example of semantic coupling is two connected components sharing a business domain model. If the model changes, both components have to change, which is similar to stamp coupling in structured design.
Khononov summarizes by noting that the interplay between the three dimensions that describe the nature of relationships between two components affects how hard it will be to maintain that relationship. To minimize, we must eliminate accidental coupling, reduce connascence as much as possible, mitigate volatility with integration specific interfaces, and reduce distance.
Khononov recommends the book Composite/Structured Design for anyone interested in microservices. Although the book is 40 years old and doesn’t mention the term microservices, the underlying design principles discussed in the book are the same as for microservices.
The slides from Khononov’s presentation are available for download. Most presentations at the conference were recorded and will be published during the coming months.