Do you have disguised Coupling in your system?

Łukasz Monkiewicz
6 min readNov 7, 2021

--

Coupling, in a way, is a metric of how easy it is to modify your system. It shows you how many problems you will encounter during future development or how serious breakdown you will have to face in case of error on production. Unfortunately, wrong understanding of good practices can introduce serious coupling issues to your system.

What is Coupling?

Coupling is a term that describes how elements of your system are linked together. It can be used to describe relations between classes, modules, libraries, microservices, API and practically any element that interacts with any other element in any way. The big problem of recognizing coupling is that there is no way to measure it in a system. You won’t get a number or some kind of score that could tell you if you are on a good path. You need to learn to notice patterns in your system and develop some kind of feeling or intuition that will help you recognize coupling in the making.

On one side of the coupling spectrum, you have Tight Coupling. This means, that practically any change in one element will involve a change in another part of the system. Also, it means that such element needs another element to work at all.

You want to avoid having that kind of coupling in your system!

On the other side, you have Loose Coupling. This means that we can easily modify every part of our system. Connection point between elements is minimal and most operations can be performed without cooperation of other elements. You can say, that elements are mostly independent.

That is the setup that you want to have in your system.

Even good practices can introduce serious coupling

Using good practices is good, that's obvious. But you have to remember that these practices are good in a certain context. Good practice of monolith development can be terrible for microservices etc.

Let’s take a look at a few practices that can cause serious trouble for microservices.

Shared Model Lib

Idea

Create a library that will contain our DTOs for communication between services, and include this library as a dependency in microservices.

Issues

  • Enforces framework use
    Since it is a model of communication, there is a high possibility that these classes will also contain information or mechanism for serialization and deserialization. That means that we enforce the use of specific framework.
  • Blind introduction of changes
    Let’s say you need to change a field in DTO, you change it and release a new version of the library. Later in some other microservice another change is needed, so it is done and released. After you update your dependency to the newest version, you will encounter former changes that break your microservice. You didn’t know about them because you had no need to upgrade to the newest version. Also, the first change was done without knowledge that it breaks another microservice.

These two issues introduce very tight coupling to our system. We enforce a concrete framework to use, and also we introduce a need to test every microservice when any change is introduced to the library.

Alternative

Use Open API, AVRO or Protocol Buffers for schema definition and generate code from these specifications. That way, you have a complete control over code and technology.

Single Source of Truth

Idea

There is ONE microservice that takes care of X and you should always ask this service if you need something from X.

Issues

  • Must be always available
    If you always need to ask it, then it always needs to be available. If it goes down, all parts of the system that use it will go down as well.
  • Increased traffic
    All services using this service will have to call it using some kind of protocol. That means that inbound network traffic to this service will be increased.
  • Lower responsiveness
    Every operation involving communication with X will have lower performance. Even if such communication takes only 1ms, it will add up to serious values.

We introduce here two serious problems. Runtime Coupling, which means that service X is needed for service Y so that it can work at all. And on top of it, we also introduce Temporal Coupling, which means that both services have to be alive and well at the same time!

Alternative

One of the best solutions is to use a local copy of needed data. We can create such a copy ourselves by using Events emitted by the Source of Truth microservice. Such copy would always be almost up-to-date and should be good enough for situations where having data actual to the 1ms is not critical.

Depending on the frequency of data change, we can even go with using Cache on our side, that way we have full control on the mechanism, and we are not dependent on any events from other side.

Data model in domain

Idea

Let’s use data model classes from REST API or DB Entities in our business logic, since it has the same attributes anyway.

Issues

  • Business logic soaked in technical details
    Instead of focusing on simple business rules and problems, you will have to solve them using data models designed for different purposes. And this makes it much harder than it is needed.
  • API or database schema change introduces big refactor
    Whenever anything changes in your API or database schema, you will also have to change all the business logic! And don’t even try to change protocol or database engine to any other.

Alternative

Define separate models for your API, persistence layer and business logic. Use principles of Clean Architecture or Hexagonal Architecture. Separation makes everything much easier to maintain, and additional effort involving mapping data object from one to another is not big, since there are many libraries that will do it for you (ex. Mapstruct for Java)

XYZ Util/Connector

Idea

Let’s create tools, libraries, connectors etc. that implement and support all our protocols, algorithms and schemes, so that each microservice will only have to add this library, and it will work out of the box.

Issues

  • New abstraction layer
    We are creating a brand-new abstraction layer on top of existing abstraction layers for connectors, tools etc. that are already available on the market.
  • New custom API to do XYZ
    Onboarding of new developers will be much harder since no one outside your company will not know how to use it.
  • Hard to modify
    Introducing any changes or fixes means that you will have to update the library and test all the microservices that use it to be sure that you didn’t break any.
  • Enforced technology
    Such library enforces use of certain technology, libraries with certain versions. That way, you make it hard for other teams to develop their microservices and almost impossible to update their frameworks to the newest versions if libraries is not updated constantly.

Alternative

Create documentation describing your algorithms and protocols. Describe how stuff has to work. If it is too complicated to be described, then maybe it just overengineered and should be simplified?

Use industry standards instead of creating your own. If you want to enforce some kind of conventions (ex. Kafka topic names) than maybe enforce it on DevOps or audit levels and not through the magical library.

Even if you are convinces that such library is needed, then provide it as tools, ready to use classes and utilities that someone can use any way they want. Do not make a library that automatically configures everything just after adding it as a dependency.

Conclusions

All these patterns I have encountered in different projects. These were really perceived as a good practice and nothing wrong was seen in them. At the same time, teams had problems with independent development because of many coupling issues introduced by approach such as these.

Be careful what Good Practices you use and, especially, how you use them.

--

--

Łukasz Monkiewicz
Łukasz Monkiewicz

Written by Łukasz Monkiewicz

Architect, Developer, Software Engineer

No responses yet