Coupling can take your system either towards complexity or
towards modularity. Coupling as inherent part of
system design. Not something that is
necessarily good or necessarily evil, but something that is
needed. We need coupling.
It's like a glue that holds system together.
When people are talking about coupling, or rather decoupling,
they mean let's take something big and break it apart into
those tiny parts. Let's take a monolith and break
it into micro services. However, if you're not following
a proper decomposition criteria for drawing the boundaries of
those resultant smaller components, you will end up with
sharing lots of knowledge. As a result, you will end up
with lots of cascading changes and as a result your system is
going to be full of complexity. A modular system is a set of,
let's say, modular components, but working not only towards the
goal of that system today, but it is also designed in such a
way that we'll be able to support goals that will be
defined in the future. Hello, guys, I'm very happy to
bring back another repeat guest to the Technical General
Podcast. Today we have Vladik Kononov.
So flat, really looking forward for this conversation.
I still remember our first conversation, right?
It was probably two years ago. Your episode is still within,
you know, top ten. And I'm really glad to speak to
you for your new upcoming book, Balancing Coupling and Software
Design. So welcome to the show.
Hey, Henry, thank you so much for having me again.
And wow, time flies. Two years.
Yeah. And I remember two years ago we
were talking and I said that the coupling book is going to be
finished soon. And here we are two years later.
Yeah. So that I think it's been quite
a while, right. So maybe in the beginning you
can share apart from this book, anything else that you did
probably interesting for you to share with us?
So I'm mainly known by my work in the domain of DDD Domain
Driven Design, and that's primarily the Learning Domain
Driven Design book and the Cardboard Domain Driven design
and people who are on Twitter, maybe they will be able to find
out what that means. Right now, in last three years,
I've been working very hard to finish the book on Coupling.
Hands down, that's the hardest project I ever worked on.
And I can't believe I'm saying this, but it looks like it's
almost done. Right.
Yeah. So in preparation of this
conversation, I actually had a chance to read the book.
I think I would say almost, you know, end to end, cover to
cover, although it hasn't completed right on O'Reilly in
the beginning, I was actually quite interested to actually
figure out what exactly can you talk about coupling right?
So it seems like almost everyone in the UNI learned about this
coupling, right? But probably only a little bit,
right? Like OK, coupling, cohesion,
object oriented encapsulation and things like that.
But you actually covered it in one book.
And after I read it, it's actually very, very insightful.
So let's try to engage a conversation about coupling
today. So maybe in the first thing,
right, we all know about coupling.
Maybe we learn it in the university.
What else that you are trying to explain actually by writing this
book? Yeah, so coupling, I remember
when the idea for this book came up and I was talking to my
friends about it and one of them said coupling, like, OK, so
coupling is bad and do you need like really 300 pages to say
that that is bad? But as they say, the devil is in
the details. So it happens quite often that
we're using certain terms without fully understanding
them. Like we have that gut feeling
about something. So we are using that in our
day-to-day language. However, we are not able to
clearly describe what that something means.
And that's something for me was coupling and cohesion for many,
many years. I remember when I attended the
first ever meet up, I attended, it was about coupling and
cohesion. And yeah, I came out with this
feeling, that gut feeling level. I understand what that means,
but how can I explain what coupling is?
Why is it bad? What is cohesion?
Why is that good? So that's why I wanted to write
this book and share my findings, my research on this topics.
And if you're going to read the book, you'll see that although
it's called balancing coupling and software design, it's main
topic is complexity and modularity.
I want to show what complexity is exactly in software design
because again, that's another thing that we understand on the
gut feelings level, but it's challenging to describe and it's
even worse for a modularity. Like modularity, OK, they
started talking about it in 60s, seventies, 80s, and today we all
know that that's the goal we should strive towards.
But what is modularity exactly? Like how can you put a number on
a design? How can you evaluate whether
it's modular or not? So that was my goal, to show how
these two things are actually, or rather two sides of the same
coin. And that coin is coupling.
Coupling can take your system either towards complexity or
towards modularity. And yeah, for that I had to
write almost 300 pages. Right.
So I think maybe let's try first of all to define the terms what
is coupling, right? Because I think people know
about low coupling, high cohesion, right?
It's always like the terms that you always use, right?
Good code means like low coupling, high cohesion.
Maybe people associate coupling with just, you know, maybe
functions or classes, design and things like that.
But in your book, it is actually beyond just code level, right?
It can even go up to organization level or different
kind of services, right? Maybe let's start with the
definition 1st. In your book what is the
definition of coupling? Yeah.
So let's say we're working on a system.
A system is a number of components that are working
together to achieve a goal. That goal is the reason why that
system exists. Now, in order for those
components to work together, they need to be connected,
right? Because they cannot be fully
independent of each other. That will be just a collection
of components. That's it.
But to make the value of that system higher than the sum of
its parts, we need to connect them.
And those connections are basically coupling.
If you go to the dictionary and look up that word coupling,
well, you'll see that initially what it means is not manolith,
not big bowl of, but it means connection.
If two things are coupled, they're connected.
So I will ask you for the rest of our discussion, at least for
the next 4051 hour. I don't know how long we're
going to talk, but for the rest of this discussion, let's treat
coupling as inherent part of system design.
Not something that is necessarily good or necessarily
evil, but something that is needed.
We need coupling. It's like a glue that holds
system together. We need it.
However, how we're designing those interactions, how we're
designing those connections between the components, that's
where it gets interesting. That's where that coupling can
take us, either towards comlexity or towards modularity.
Right. So I think it's interesting that
you mentioned about connectedness, right?
It doesn't mean that when you have coupling, it's actually
bad, right? Because coupling is necessary
whenever you build software design because that's how
components actually work in collaboration with each other,
right? And you mentioned about
connectedness in your book in terms of there are two things
that probably are related when you talk about coupling.
The 1st is about shared life cycle and the other one is about
shared knowledge. I think having an understanding
of these two is really critical. Before we move on to the next
section, maybe you can talk a little bit more about this
shared life cycle and shared knowledge.
Yeah, of course. So first of all, let's assume we
have two components in the system.
Now I'm purposefully using these abstract terms components in the
system. I'm not telling what these
components are. They can be services, they can
be objects, classes, even methods within a class.
And same is true regarding this system.
It can be a distributed system, it can be a module or an object,
an object oriented codebase, doesn't matter.
So let's say we have two coupled components.
Now when we are saying that coupling is bad, what we usually
imply is that we have to change those two components
simultaneously. For example, you want to change
one of them, but then you're forced to apply a corresponding
change to the second component, right?
And that connects their life cycles.
They have to be evolved simultaneously.
Now, is that something we are interested in?
It depends. We'll talk about it a bit later,
I assume. But right now, let's assume that
coupling introduces that dependency in the life cycles of
connected components. Now, propagating changes in
almost all cases is not something that we want.
We want to be able to modify each component individually.
Once we introduce those connections between components.
In order to work together, they have to share some knowledge
about each other and the amount of knowledge they share.
It actually dictates what is the amount of cascading changes that
are going to follow because of that design.
That knowledge can be as simple as a knowledge of integration
interfaces. Let's say I have a module, so I
define its public interface. You want to work with it, you
have to know the details of its public interface.
Or maybe, let's say you want to ignore that public interface and
you want to use some other means of integration.
Let's say you want to reach out to my database.
In that case, maybe you want to, you will have to know the
implementation details of my module.
So that's the other extreme of knowledge that can be shared.
And of course, if I'm sharing only the integration interface,
then the amount of cascading changes that are going to follow
is going to be dramatically lower than in the second case
where you're coupling yourself to my implementation details.
So overall, these are two properties that are very
interrelated. The first one is how connected
are the life cycles of those connected components, and the
second one is what is the amount of knowledge that's been shared
across the component's boundaries.
Yeah, thank you for explaining this.
I think it's such an insightful concept when I read it in the
first few chapters, because maybe the life cycle is kind of
like straightforward, right? Whenever you make changes, if
you have to propagate into multiple places, right?
It could even be services or multiple teams.
That is actually pretty bad. And we know that it's actually
tightly coupled, right when we do that.
But actually the most important thing that I find really
interesting is about the shared knowledge, because there are
explicit knowledge that you share, maybe about business
requirements or maybe even the interface or contract APIs,
right, when you interact with APIs.
But also there's implicit knowledge that probably is a bit
tricky to identify. And that's why you find it
difficult to explain the coupling by just looking at the
code, right? Because it's such an implicit
thing and let's try to actually analyze in terms of coupling and
complexity, right? Because people associate
coupling a lot with the complex code.
So you try to explain it with Kinovin framework, right?
So maybe a little bit of background, why is it such a
relevant to talk about Kinofin when explaining this coupling
and complexity? Yeah.
So first of all, I want to go back to your first comment about
the relationship between life cycle coupling and knowledge,
because I think it's super important what you said here,
because when people are talking about coupling or rather
decoupling, they mean let's take something big and break it apart
into those tiny parts. Let's take a monolith and break
it into micro services. However, if you're not following
a proper decomposition criteria for drawing the boundaries of
those resultant smaller components, you will end up with
sharing lots of knowledge. As a result, you will end up
with lots of cascading changes. As a result, you're going to end
up cursing that very day you started decomposing it.
And as a result, your system is going to be full of complexity.
Now, complexity is something that you know it when you see
it. But again, we need something
more precise, something that we can explain.
So that's why I chose to use Kinemic Framework.
I really like how this framework, originally it's a
decision support framework. It provides with different, we
are talking about software engineers, so let's say
different scripts for making decisions and how to make
decisions in different situations.
And it categorizes those contacts into five domains, each
one requiring unique approach to making decisions.
If let's say you are working on a system and again we are
software engineers, let's say we're working on code base and I
want to make a change. If I know exactly what the
effect of that change is going to be, then that code base is
not complex, right? It's probably simple.
That's why this domain, in kinetic terms is called a clear.
It's something that is simple clear.
If I'm making a change, I know what's going to happen.
If, on the other hand, I am looking at the code base and I
have no clue what's going on there, however, I can consult
somebody, some external expert, and that expert will tell me
exactly what's going to happen. Then again, it's not complexity.
This time we have a complicated system.
For example, let's say I am looking at a code base written
in Go, or I don't know Scala. Even though I tried to learn
these two languages, I would still prefer to call an external
expert to help me out to understand what's going on
there. So again, that's not a reason to
call it complex. That's something that is
complicated. And we arrived to the third
domain, which is a complex domain.
Here we have no idea what the effect of a change is going to
be. Also there is no expert we can
consult. So the only option we have is to
conduct a safe experiment, which means to make a change and
observe it's results and based on those results to try and
understand it's impact. So that's complexity.
Now in this domain, we assume that there is a connection
between an action and it's outcome, A cause and effect.
Now in the fourth domain, which is called chaos, there is no
relationship between A cause and effect, the effects are random.
Now, such things do happen in software of course, especially
in production environments after deploying Fridays or on public
holidays. However, this is something that
is rather not related to our discussion of software design.
It's more related towards the runtime of the system.
But in software design, when we're changing the system, it
should be somewhat deterministic one way or another.
And finally, the 5th domain of Canavan, it's unknown.
When you're looking at the domain, you have no idea where
you are. And in this situation you may
assume that it's clear, it's something simple, but as I say,
devil is in the details. And when you get to details,
it's only discovered that it's complex domain or chaotic domain
or whatever. So these are the five domains.
And I really like the way Dave Snowden, the author of this
framework, defined complexity when the only way of identifying
outcome of an action is conducting safe experiments.
Unfortunately, that's something that is pretty frequent in
software design when you're looking at the code base and you
want to refactor. Well, if you know what's going
to happen, great. Maybe it's complicated and that
expert is the compiler. Maybe compiler can tell you
what's going to happen or a colleague that build that code
base in the 1st place. But in many cases, when we're
working with legacy systems, brothel projects or big bowl of
Muds, we make a change. And I had a friend I still
remember that I was working at, It was actually my first, like
real workplace. And that colleague of mine
showed me, look, before doing something important, I have
these stones. I place these stones on my
keyboard for good luck. So once you need those stones
for good luck, you're in complexity.
And yeah, you need to find a way out of it one way or another.
Right. I think that's a very relatable
joke, right? And some people also like think
of it like test in production, right?
It you will only know once it hits production with the real
traffic, real data and real user behaviors.
So I think, yeah, when you find that kind of situation, probably
it's more about complexity, right?
It's not about complicated, it's not about something like chaotic
as well, right. So I think this kind of
framework really, really good to picture what kind of a domain
you are dealing with. And thanks for pitching in about
this splitting, because people normally when they deal with
coupling, they will say, OK, I will just split more, you know,
like micro surveys, for example, or create more classes, create
more abstraction. The other thing about dealing
with coupling for some people, right, they build more
flexibility when you say coupled or it means like difficult to
change. So people try to build more
flexibility, degrees of freedom, right?
In your book, you actually find that these two extremes actually
is very dangerous and it might lead to complexity in terms of
code, right? And you classify this sometimes
as accidental complexity. The other one is essential
complexity, right? So tell us more about these two
different complexity as well, because I think it's really
important for software engineer to understand essential and also
accidental complexity. Yeah, of course.
So let's say that you're working on a system for a non trivial
business domain. Because come on, nowadays if if
the business domain is trivial, JGPT is going to do it.
So assuming that you are working on a non trivial business
domain, there will be some complexity in it, complexity
that is driven by its requirements.
For example, let's say it's financial system.
That domain is not simple or a medical system.
Even worse, if there's something interesting that the system is
doing, usually it involves some complexity.
That's the reason the system is being built, in order to help
its users. To encapsulate that complexity
in the system to take care of it.
So that's the essential complexity of that business
domain of the problem you're solving.
There is no way of avoiding it. If you avoid that essential
complexity, then your system is not doing it's job.
Why do I need it if it's not going to help me to take care of
that complexity, right? We cannot avoid it.
So we have to manage that complexity.
How can we manage it? Well, we have plethora of tools
from domain driven design to design patterns, whatever.
We can talk in details about each one of them, but we'll need
them 24 hours at least. So that's the essential
complexity. Again, something that you
manage. The accidental complexity on the
other hand is something that you introduce, something that you
introduce by not designing your system properly.
And again, if we define complexity as that implicit
relationship between an action and its outcome, then accidental
complexity is designing a system in such a way that whoever is
going to work on it next, or maybe even me tomorrow, won't
have any clue if a line has been changed, what effect of that
change is going to be on other components within the same
system. Or maybe it's interactions with
other systems which are even worse and even harder to predict
than effects within my code base.
Again, it's all matter of design of how you architect your system
and how you design those interactions.
Which means those connections, which means coupling between
it's components, whether it's going to introduce that
accidental complexity or on the other hand, it will help you to
manage that essential comlexity. Right.
So I think when we talk about complexity, Please remember in
your mind, right? Are you talking about the
essential complexity, which is actually the problems that
you're trying to solve? Maybe it's the business
requirements, maybe it's like a functionalities that you have to
build an algorithm, right? Or are you actually dealing with
accidental complexity things like you weren't aware you
designed something and actually the impact is a little bit
unpredictable, right? Maybe you expect one conflict
change, but how come that you actually deal with so many
changes, cascading changes in multiple places?
So I think don't introduce too much accidental complexity and
probably coupling is actually one of the root cause why
accidental complexity can happen.
So let's move on maybe from complexity to the other one,
modularity. As you mentioned right when you
write this book, you want to cover these two areas really
important. So I think modularity also kind
of like covered a lot right, in basic software design or you
know, programming courses. But what is something different
about modularity that you want to talk about in relation to
coupling? Yeah.
So first of all, to define modularity, what is it?
If you look up that word in many places you will find the
recursive definition of like a modular system is one that build
of modular components. Thank you for that, that really
really helps me. On a serious note, you may also
find definition saying that a modular system is one that is
built out of interchangeable components.
A system that resembles Lego bricks that you can connect with
each other and form something else.
Now let's say you're working on a typical knowledge management
system for a business. It doesn't resemble Lego bricks.
I don't think so. Do you need interchangeable
components? Maybe you will have to change
the database one time because you will need to run your tests
on in memory database, which is not going to be a real one.
But that's it. That's probably the limit of our
interchangeable components. Maybe you should encapsulate all
the managed services of your cloud vendor so that you can
migrate to another cloud vendor. Maybe it should, but trust me,
those abstractions you're going to introduce are going to be
leaky anyways, and that migration is not going to be as
simple as people hope. So still doesn't mean that we
don't need modularity. Now.
We definitely need modularity. However, we also need to define
what we mean by it. And here I want to go back to
the definition of a system that I started with early on saying
that a system is a set of components working towards a
goal. A modular system is a set of,
let's say modular components, but working not only towards
it's goal, the goal of that system today, but it is also
designed in such a way that we'll be able to support goals
that will be defined in the future.
Now to support goals that are going to be defined in the
future doesn't mean that out-of-the-box it has to
implement all possible use cases.
No, of course not. And that's not possible.
The design of that system should be able to withstand
implementations of those future use cases, those future
requirements. Now this begs the question, OK,
so let's say I'm working on knowledge management system for
a business in financial domain. It doesn't mean that my design
has to support the option of that system turning into a
driver for a printer in the future.
Of course not. That's not a goal that you have
to think about. So when thinking about
modularity, we have to take into account future goals, but they
have to be reasonable. How do we identify those
reasonable changes? Well, that's another thing that
makes our job so hard. So we have to think about those
future changes. Which changes are going to be
reasonable and which are not. Of course, the example I used of
a printer driver is a simple one.
But yeah, real life is a bit more complicated.
So again, going back to the definition of modularity and
modules and modular systems, the goal is to design a system in
such a way so that it will support future requirements.
Once it does, then we can say that those components it is
composed of are actually modules.
So that brings me into another problematic topic of modules
versus components, because I define the system as a set of
components and the modular system as a set of modules.
Now if you look up on the Internet the difference between
these two terms, you will see some problematic definitions.
Some sources claim that modules are, let's say, logical
boundaries, whereas components are physical boundaries.
Now this thinking model can be useful in some contexts.
However, if you go back to the origins when the term modularity
was introduced to software engineering, you will see that
back then they had three properties of a module.
One of them was that it should encapsulate a behaviour, meaning
that we have some behaviour that is encapsulated in one module
instead of being spread across multiple modules.
Second, it has to expose an interface through which other
parts of the system can execute that functionality.
And 3rd, it should have potential of being independently
compiled. Only potential.
It doesn't mean that whether you're compiling your module
independently turns it into a component.
No, it is still module. Again, that definition is, in my
opinion, it's flexibility is very useful.
Yeah. So the difference between
components and modules, it's not about the type of boundaries,
whether they're physical or logical.
It's about encapsulating functionality.
And that's something that is essential for managing the
essential complexity and not introducing accidental
complexity. Right.
So I think that's really insightful, right?
This is the first time I think I kind of like find the definition
of modularity, you know, like the concept of knowledge
boundaries, right? And you understand like the
difference between modules and maybe components and things like
that, right? And I think it's very important,
right? When people talk about
modularity, most of the times what they're doing is actually
like create more abstractions, things like in object oriented
programming, right? When you want to create more
modular system, you create more abstractions, right?
You introduce maybe interface, you know, abstract classes and
things like that. First of all, is this the right
way? And second thing, right, in your
book you mentioned about module is actually a knowledge
boundary. So I think this is also
something very insightful, right?
You are not talking about physical logical boundary
anymore, but it's all about knowledge boundary.
And maybe this is related to the shared knowledge that we
discussed earlier. So tell us more about this
abstraction and also about this knowledge boundary.
Yeah, sure. So introducing abstractions it
can be beneficial, but it can be a very effective way of
introducing accidental complexity.
Again, whether you're using it to manage the essential
complexity or introducing new type of complexity depends on
your concrete context. Now, one part of me want to say
it depends and then advance to the next question, but I'm not
going to do it. So let's talk about what it
depends on. So let's say I have two
components and I'm thinking whether they're going to
interact with each other directly or should I introduce
an abstraction between them that will encapsulate something?
Now we have to ask, will that additional abstraction help us
to encapsulate knowledge or in terms of David Pranas to hide
some information, whether it will help us to use the boundary
of one of the modules as a boundary of knowledge?
So let's say I have a module that implements some
functionality that is not trivial.
Let's say it's going to be about encryption.
Yeah. And let's say I made that very
smart decision of implementing my own encryption algorithm.
So one way for us to work together would be, I tell you,
Henry, yesterday I had a few beers and I was thinking about
that super secure encryption algorithm.
So from now on, I'm going to communicate all the information
encrypted with that algorithm in order to decrypt it.
Here's what you have to do. And then I brain dump all that
after beers thinking about that encryption algorithm on you and
you have to implement in your code base in order to decrypt my
data. What's going to happen is we
have that knowledge of that probably dumb encryption
algorithm duplicated in two places.
One place is my module, which encrypts data, and the 2nd
module is yours that has to decrypt that data.
Of course, since I made that smart decision of coming up with
an encryption algorithm, it's going to change.
Probably there are going to be a security patch, let's say in
after a few days. So now I will apply the change
in my code base and I'll call in and say, Henry, that's urgent.
You have to modify a few lines in that algorithm because
otherwise you're not going to be able to decrypt my data.
That's the effect of duplicating knowledge.
Now, of course, that's a simplified example.
In real life, we're talking about a business domain entities
communicating with each other. But that is the same.
We have that knowledge that is duplicated.
Now we could have used a module to encapsulate that knowledge to
hide that encryption algorithm in one place.
And in that case, the functionality of that module
would be encryption and decryption.
It's interface would be probably 2 methods for encrypting and
decrypting data, and it could be independently compiled, or it
could be compiled within our monolithic code base.
It doesn't matter, it's still a module.
Now, if we compare the details of an encryption algorithm
versus having an interface with two methods, encrypt and decrypt
data, the difference between the two amounts of knowledge is
substantial, right? In that case, that abstraction
is going to be super useful. It manages the essential
complexity. That encryption algorithm is
encapsulated, so it helps us to make our system simpler because
all those other components that will have to use it, they don't
have to be aware of the internal details of that logic.
On the other hand, let's say we're building a system and that
pattern data transfer objects, it gets lots of hate.
Like people are saying OK, so I have my business entities and
then I have to introduce Dtos that are going to be on the
boundary of my application and my APIs are going to return
those Dtos right Now why people love to hate Dtos?
Because in their code bases, usually what they are doing is
translating one data structure to another data structure which
looks exactly the same. So you have just basically
copying values from one data structure to another one.
However, as long as the attributes of those data
structures, those types of those attributes, are exactly the
same, we're not actually encapsulating any knowledge by
introducing that abstraction. What we're doing is we're
introducing another moving part. We're introducing another object
or another class that we'll have to think about.
When we are going to change our model, we'll probably have to
apply the same change. Let's say we want to change the
name of a field in both places. Now in this case, that DTO is
not an effective obstruction. It increases complexity.
It introduces accidental complexity.
Now, am I saying that DT OS are bad?
Of course not. I just gave you an example in
which DT OS are useless. Sorry, maybe not useless, but
they're not providing the value. But on the other hand, they're
introducing another something you have to think about.
And the more things you have to think about, the more the
cognitive load and our cognitive load limits are not looking
good. There were studies done in 50s,
then repeated in 2000, and they were not that spectacular early
on and the number was reduced 50 years later.
And yeah, So going back to your question of abstractions, it
depends. If abstraction helps you to
encapsulate knowledge, it's effective.
If it doesn't, then it's just going to introduce additional
moving parts and you'll end up increasing the accidental
complexity of the system. I'm so glad that you brought up
the DTO discussion, right? Because I think, yeah, it's like
a love hate kind of a pattern for some people, they find it
like a more work in terms of creating the APIs.
You know, you have to build this translation, which is probably
the same when you build the system first time, right?
It's like mapping the same kind of data structure.
But I think if you think about it, if the system evolves, the
API evolves, and your implementation details changes
over time, maybe the database, how you store the data changes,
right? The detail is like a boundary,
right? A contract that you probably
pass on with the other teams or the other service, and that
doesn't change as long as you create this proper abstraction.
So I think thanks for bringing that up.
Let's move to the next topic. I know that we can probably talk
a lot more about complexity and modularity, but I think I want
to bring up another fascinating thing that you brought up in the
book, right? It's actually how to measure
coupling. So in the beginning, you
mentioned like sometimes it's very hard to actually put a
number of how coupled is our code, right?
It's always like a, you know, rough idea, maybe estimation.
But actually in your book, you bring three different dimensions
that we can use to kind of like quantify complexity, coupling
and things like that. And you brought up the point
about integration, strength, space or distance and time or
volatility. So maybe, I know it's pretty
hard to explain all three at the same time, but maybe if you can
elaborate, what are these 3 dimensions dimensions, How can
we use them to actually assess the complexity or the levels of
coupling of our code base? Yeah.
So evaluating coupling is something that we are trying to
do for ages now, I think since 60s when the structure design
methodology was coined. They started working on it in
late 60s. So they introduced a model for
evaluating coupling. Then there was kinesins and then
there were some metrics and those metrics try to put a
number on on coupling. The problem with that approach
is that it was based on counting variables or counting methods.
But is that number something that you can really, really
trust? And there is a talk that I did
at a few conferences with my friend Sonia Natanzon in which
we're talking about software design metrics and basically
saying that you shouldn't trust them.
And there is a metric called stability, which is about the
relationship between the incoming connections and
outgoing connections, afferent coupling and afferent coupling
as an ASL speaker really hate these terms, like passionately.
That metric says that if there are more modules that depend on
you, then the number of modules you depend on, then your
stability score is going to be higher.
So in that talk I'm showing an example which is supposed to be
perfect from that sense. Like a module that let's say 100
other modules depend on it, and our module only depends on one
other external component. That's it.
So the ratio is one to 100. So it's supposed to be super
stable. However, again, as I mentioned a
couple of times, the devil is in the details.
And on the next slide I'm showing how that one dependency
is implemented. And what I'm showing is that it
uses reflection to read a value of private field.
So what's going to happen? Well, once I'm introducing that
dependency on an implementation detail, any change the
implementation details of that external module has the
potential of breaking my component.
Now, once I have something that has to be changed in my
component, then I have 100 other dependencies that are probably
going to be affected by it. So now what we're getting is a
perfect stability score. Ended up being big bowl of mud
basically. So that's why initially I try to
avoid putting numeric values on coupling, because it's all about
details, Details that are hard to quantify and hard to
describe. So instead, in the book I
propose a different model of evaluating coupling that is
based on three dimensions as you mentioned.
The first one is the dimension that measures the knowledge.
What is the amount of knowledge that is shared between two
connected components? How do you measure knowledge?
Can you put it on a scale and have a number next to it?
Of course not. Maybe someday, I don't know.
By the way, when I was doing talks at conferences on this
subject, almost always somebody from audience says that, hey,
you really have to think about a way of doing this automatically,
of evaluating knowledge in the book.
I said, OK, I tried, I failed. That's topic for further
research. If you want to do it, good luck,
I'll be happy. So knowledge.
So we cannot put a number in it yet.
However, we can look at that early work, back from late 60s,
early 70s, the structure design methodology, and back then they
introduced a model called Module coupling.
It had 6 levels that are kind of challenging to apply in our
modern systems because that model is based on languages such
as COBOL and Fortran. It's like fun, but the opposite
of fun. Instead of having to learn those
languages and what those keywords mean and how to adapt
to translate them to our world. In the balancing coupling book,
I proposed a different model. I called it integration
strength. It is based both on structured
designs model and another one. I'll talk about it in a minute.
So basically it adapts the six levels of integration strength
into terminology. That's going to be more
convenient for us today. The four terms are contract
model, functional, and intrusive coupling.
They are not defining the amount of knowledge, they're defining
the type of knowledge. So let's start from the biggest
one, intrusive coupling. This one means that I'm using
something other than public interfaces for integration.
Let's say I'm using reflection, let's say I'm using another
micro services database directly, or something else,
whatever that wasn't intended for integration.
I'm introducing an intrusion into it's boundaries.
That's why it's called intrusive coupling.
Now, once we are introducing intrusive coupling, we have to
assume that the author of that module might have no clue that
we're doing that thing, which means almost any change that
they're applying has the potential of breaking the
integration. So lots of knowledge.
The integration interface is fragile.
It's implicit, so expect cascading changes.
Another type of knowledge that can be shared is the knowledge
of functional requirements. I called it functional coupling.
If previous one was about how that upstream module or
component is implemented, this one is about what is the
functionality that the component is implementing.
Now let's say that we have two components and they implement
closely related business functionalities.
That means that probably they will have to change
simultaneously because of the same changes in business
requirements. That means we have functional
coupling. An extreme example of functional
coupling would be let's say we have the same business rule
implemented in two places or the same business algorithm or the
same business invariant. And from the business
standpoint, if the requirements or the definition of that rule
changes, they have to change simultaneously because otherwise
the system is going to be in an invalid state.
In that case, we have a very strong case of functional
coupling. By the way, they don't have to
be physically connected. I call it wireless coupling.
Another case of functional coupling is, let's say we have
multiple operations that require concurrency control, probably
because they're going to work on the same set of data, right?
In that case, again, functional coupling.
Or maybe we have operations or functionalities that have to be
implemented in a specific order, one after another.
That's also case of functional coupling.
That requirement of being executed in a specific order is
probably there for a reason. Probably it introduces some kind
of business dependency there. So that's the level of
functional coupling. Here.
We are sharing the knowledge of our business requirements.
Now to implement those business requirements, usually we have to
model a business domain. We have to understand what's the
system we are implementing and define a model that represent
that business domain. And then we are going to
implement the functionality, the requirements in code using that
model. If you have two components that
are based on the same model, which means if the model changes
both of them have to change, then you have model coupling.
And finally the lowest level is contract coupling.
So contract coupling we can think about it as a model of a
model. Remember the discussion about DT
OS? I used an illustration of an
ineffective DTO and then you handry elaborated and discussed
an effective use of DT OS. In that case, those effective
details are contracts, integration contracts.
That's a model of a model that was crafted with the purpose of
encapsulating that model that's being used internally.
Whenever that model changes, we can contain those changes behind
the same integration contract. So we are minimizing the
knowledge that we are sharing across our boundary.
So overall, these are four types of knowledge.
We can share the knowledge about our integration contracts, about
how we see and how we think about the business domain.
It's model, model coupling. Then we can share knowledge
about our business requirements, functional coupling.
And finally, we can share knowledge about our
implementation details and that's intrusive coupling.
So overall, these 4 levels are not going to put an exact number
on the weight of knowledge you're sharing.
However, these are four different types that signal
different amounts of knowledge that's been shared.
Now if you're going to read the book, then you'll see that each
one of these types has also degrees except for intrusive
coupling, functional model, and contract coupling.
They have degrees so that first of all you can compared to
designs of the same integration strength and also to give you a
more fine grain control on the knowledge that's being shared
there. So again, integration strength
overall 4 levels and three of them have degrees.
Now the four levels are based on the structure designs, module
coupling model from late 60s, early 70s and those degrees that
I'm using for functional model and contract coupling are based
on a model called Kinesis which was introduced in 90s.
So integration strength kind of combines both.
Let's say that you evaluated that knowledge using integration
strength, You have two components and you identified
what is the knowledge that's been exchanged between them.
Does it say that let's say functional coupling is
necessarily bad, or model coupling, is it worse than a
contract coupling? Well, it depends If you can
reduce it, of course you have to reduce it.
If you can turn a model coupling into concert coupling, probably
you should do it, but not always.
Sometimes you have to share a model, or sometimes you have to
share business requirements, right?
Does it mean that your design is bad?
No, it depends on the next dimension, the dimension of
distance between those connected components.
Now, since the beginning of our discussion, I was using abstract
terms to describe systems. I was saying we have sets of
components. I purposefully didn't mention
whether those components are methods, objects, services, or
whole systems or whatever. That's because we can introduce
coupling across different levels of abstraction.
We can have coupling between methods, coupling between
objects, coupling between modules, namespaces, services,
whatever. Whole systems.
Now the higher we go on that layer of abstraction scale, the
higher the physical distance between the source code in which
those components are implemented.
Like extreme case, let's say you have two methods within the same
class. Probably they are going to be
close to each other, probably in the same file, right?
Different objects, probably different files, different
namespaces, different folders, different services, maybe
different repositories, different systems, maybe
different companies, etcetera. So the higher you are on that
scale feel longer the distance. Why is that important?
It's important because if you combine it with the knowledge,
you get a sense of whether you're going towards complexity
or towards modularity. Because let's say that you have
two components with functional coupling between them, which
means they're sharing a lot of knowledge and you're putting
them in separate micro services, which means the distance between
them is big as well. Now that functional company kind
of implies that we're sharing lots of knowledge.
So if something is going to change in that knowledge, that
change is going to be propagated across the boundary.
So both of them are going to be changed simultaneously.
Now, if the distance is big, it is going to be an easy change.
Probably not. The bigger the distance and the
harder it is, the harder it is going to be to imply the change.
In other words, we can say that the bigger that distance, the
more coordination effort will be needed to implement a change
that affects both couple components.
So if we have both integration strength and distance high, we
get complexity. We're looking at the system in
which we want to change the component.
But in order to understand the effects of that change, we have
to investigate components that are located far away from us and
maybe in different repositories even.
Is it easy? No.
Will it require a cognitive efforts, lots of them, right.
So that will result in cognitive load and as a result it will
result in complexity. Now what if we do the opposite
of that? Let's say we have two components
that I'm not sharing knowledge. Let's say we are on that
contract coupling level. So we're putting them close to
each other in the same a module, the same namespace, the same
package with the recalled. So both values are low.
And if all we have is 2 components, that probably, yeah,
who cares. But usually in real system, it's
not going to be two, It's going to be way more.
And once you have way more unrelated things located close
to each other, then when you have to make a change, you
suddenly have to find that thing you have to change, right?
And the more options you have, the higher the cognitive load,
the higher the cognitive load as a result, the higher the
complexity. So at this point, we can
identify complexity as a situation in which integration
strength is equal to distance, both are low or both are high,
we get complexity. Now, what is modularity event?
Well, modularity is the opposite of complexity.
If you're working on modular system, you should know exactly
what the effect of a change is going to be.
So we can apply it here as well. Let's say that if complexity is
in the case of both strength and distance being equal, then
modularity is when they're not equal.
And again, extreme examples, if you have high integration
strength and there is no way for you to reduce it because that's
the business domain, that's your essential complexity, deal with
it, then how can you manage it? Well, you can put those closer
related things close to each other.
You can minimize the distance between them.
Yes, they will have to change together, but once they're close
to each other, the cognitive load on you is going to be lower
because it's almost like modifying the same thing or vice
versa. Let's say we have minimal
knowledge shared across couple components.
We have contract coupling. Well, what should we do?
The distance should be the opposite.
Let's spread them apart. Let's spread them across, let's
say name spaces or services, whatever.
Instead, in those resultant services, we'll put only things
that are related to our modules, in other words, those that do
have high integration strength. So that's the relationship
between distance and integration strength.
We can use them for evaluating complexity and modularity of the
code base. Now there is other dimension,
and that's the dimension of time or the dimension of cutting
corners. I call it being pragmatic.
Let's say we have two components with functional coupling between
them. No, not functional.
Let's say intrusive coupling between them and big distance
between them. Let's say we have two systems.
Once we're talking about systems, then the distance is
big and we are introducing intrusive coupling between them.
Is that design necessarily bad? Well, that's tricky question
because from complexity standpoint, we should say yes,
right? However, what if that upstream
system is not going to change? Never.
Let's say it's a legacy system and you have to integrate with
it, and that system is dead. Like in your company, nobody has
that courage of touching it, but you still have to integrate with
it. So should you roll up your
sleeves and get your hands dirty and implement additional
endpoints for proper integrations through contract
coupling? You probably could.
However, given that that's a legacy system and it's not going
to change, it's fine to take its data from its database, for
example. So yeah, you are introducing
intrusive coupling. However, since the volatility of
that upstream system is low, then you are not going to feel
any pain in your future because of that intrusive coupling.
So overall, we have 3 dimensions.
We have integration, strength and distance for showing us the
way, whether we're headed towards complexity or towards
modularity. And we have that dimension that
can help us to make pragmatic decisions based on the
volatility of our components. Now, if you combine the three of
them together, basically if you're into domain driven
design, then supplying subdomains, generics, when
you're integrating generic subdomains, I say that it's OK
to cut cores. That's something that usually is
implemented, as Eric Evans says, with a rapid application
development framework. Is it going to be super modular?
Probably not. Why is it OK?
Because those subdomains are not going to change frequently.
Core subdomains, on the other hand, that's where I should
expect your changes and that's not a place to cut corners.
That's a place where you want modularity.
And if you follow the management design that aggregates basically
take the idea of functional coupling to extreme, we have
those transactional boundaries. So we are putting all those
entities that share those transactional boundaries within
the same aggregate bounded context are there to protect our
models. So we can use the same model of
the business domain within bounded context, but not a cross
bounded context. Cross bounded context, we need
integration contracts in the language.
These are open host service, published language or anti
corruption layer. Yeah.
And if you analyze design patterns, architectural
patterns, or cases where people are saying that one pattern is
evil and should be considered harmful versus people saying
that that pattern will save your life, well, consider those
extreme opinions from the perspective of those 3
dimensions. Probably you're going to find
the explanation for those conflicting opinions in one of
those dimensions. Wow, I think I feel like I am
listening to like a insightful lecture, right?
So I think what you just explained, right, with different
scenarios, different kind of permutations, I think it kind of
like opens up our eyes a bit, right?
Like how do you analyze complexity?
How do you analyze things that probably it's a bit difficult to
change. Probably also think about
dealing with legacy and you bring in your bread and butter
DDD concept as well, right? As you probably write through
the last chapters of the book, right?
I think all these kind of like now starting to get more sense,
right? You bring up the topic of
coupling, but also you bring up the topic of architecture design
and also DDD itself, right? I hope people by listening to
this episode are maybe better by reading your book, actually gets
more tools to actually discuss about software design.
Because yeah, these three different dimensions are really,
really critical if you want to talk about the complexity,
modularity of the system, right? And whether your system actually
can take the trade off, right? Because coupling, as you
mentioned right in the book, you cannot really eliminate coupling
because the software has to work together.
There will be a level of coupling.
Whether the high coupling is bad, again, it depends on the
context, right? If it's not so volatile,
probably it's not so bad. And you bring in the topic of
DDD. So maybe as we move on to maybe
the later part of our conversation, I hope you still
have the time, right? So in your last part of the
book, after we understand about definition of coupling, we
understand about 3 dimensions of coupling, how we assess our
system design. The last is about balancing the
coupling. Now you understand you have all
the tools and the knowledge required and your software
design, you advise us to actually know how to balance
this coupling when you make decisions in your software
architecture. Probably a little bit more
practical tips, right? Now that we know all this, how
would you advise us software engineers to think about
balancing coupling in our software design?
Yeah. So when you were making software
design decisions, at whatever level of abstraction, think
about those 3 dimensions. What is the knowledge that is
being shared? What is the distance across
which the knowledge is being shared?
And also, of course, what is the volatility of that knowledge is
going to be? How can you evaluate that
volatility? Well, that's a place where you
can use different models. I prefer domain driven design
subdomains, but there are other methods.
Now, my model of balanced coupling, it's not going to give
you a score like a number, like a grade that you can say, hey, I
implemented a system with 99% of balanced coupling.
Unfortunately not. Maybe somebody someday will join
forces and implement it, but the devil is in the details.
It's not something that is really trivial, but at the same
time I wanted to offer that model that you can keep in the
back of your head, something that is easy to remember,
something that doesn't require memorizing tons of different
patterns. All you have to remember is that
there are four types of knowledge, and that's basically,
and there are three dimensions. If you evaluate the knowledge
and you compare it with a distance, you know whether
you're headed towards modularity or complexity.
If you're headed towards complexity, then you can look at
the volatility and decide whether it's something that is
worth your effort or maybe you should focus on something else.
So overall, I would say keep these.
I hope that they are simple, these simple terms or ideas in
the back of your head when you're making software design
decisions and apply them. Again, it's not something that
is going to be easy to incorporate in continuous
integration pipeline, but it's something that is supposed to be
easy to incorporate in your software design decision making
process. Yeah.
So for different types of knowledge sharing, right, Just
to recap a little bit, there's an intrusive coupling, there's a
model coupling, functional coupling and so contract
coupling, right. So these four very important.
Maybe you should bring up these kind of terms when you discuss
collaboratively about your software design and then not
just about this knowledge sharing.
Because again, like coupling, probably the most problematic
one is about knowledge sharing, right?
The shared knowledge, the thing that we talked about in the
beginning. So after you analyze all this,
you also bring in the three different dimensions.
So integration strength is basically those four things,
right? And then you'll bring up the
topic about distance and also about the volatility.
So All in all, if you probably bring all these variables inside
your discussion, maybe you'll make a better decisions, right?
And also don't forget as well, maybe in the beginning you
design A better balance coupled system, but over the time as
because business change, you know software change which you
also covered in your book. There are so many reasons why
software will change, right? And let's not forget, when
something major change happen, you also bring up again this
topic to actually make sure that your change can rebalance your
software design in terms of coupling.
And also in the end, right, Eventually it doesn't create
like a big ball of mud or the worst case is distributed big
ball of mud, just like the crazy microservices that people are
struggling with. So I think All in all, these are
very good topics, right? For people who would like to
understand further, because I'm sure maybe just by listening,
this conversation is a little bit too short, right?
Maybe you should check out Let's book.
I think it's coming out soon and hopefully people will be better
to get a better tools, you know, like in terms of coming up with
better software design. So let's wrap up.
But before I let you go flat, I don't know whether you still
remember last time I asked you this question.
I asked you about these three technical leadership wisdom, I
think 2 years apart, right? And a new book as well.
Maybe if you can share a little bit of what kind of wisdom that
you can share with us, maybe with the theme of coupling.
So yeah, is there anything that you want to share with us?
Yes. So three tech leadership wisdom,
that's our question. But we started by discussing the
things that we kind of understand on the gut feeling
level. But it really, really helps to
get past that gut feeling level towards more explicit
definition. So if you stumble upon something
that you cannot explain clearly, I strongly recommend getting
into it, learning what's going on there because chances are you
are not alone. Usually there will be more
people struggling to define a concept.
For me it was, for example, coupling and modularity.
And once you are there, once you've found you were able to
find such term and to define it, then you should probably share
your wisdom with the world because people are going to be
grateful to you for doing that. So that was the first one.
And the second one would be about modelling.
So modelling is a very important part of what we're doing.
And as software engineers, I don't think we spend enough time
on training that muscle, that modelling muscle.
We spend more time doing workshops on Kubernetes and
Lambda functions, for example, things that are more technical.
Modelling is about our ability to understand the real world,
those real world systems that we have implemented in code.
So I would say bend time modelling, it's super important.
It's super important to train that muscle to get better at it.
And it also helps to analyse other models and models are
everywhere. Even if you are looking at a
model of toy car steel model. So think about analyse it from
that perspective. The model is not a copy of the
real world, it's human made contract that is supposed to
solve a problem. So ask yourself what is the
problem that this specific model solves?
Does it do a good job at it? If it's a toy car and then
probably it's goal is to mimic that possession of some cool
car, how cool is that etcetera with your software models.
And then of course, apply that knowledge for evaluating what I
called earlier models of models, integration contracts.
How effective are they of encapsulating knowledge?
And that will help you to become a much better software designer.
Again, at whatever level of abstraction you're working on,
it doesn't matter. The underlying ideas are the
same. And that brings me to the third
tech leadership wisdom. And that would be about design.
And design is another overloaded term that different people
understand in different ways. We have graphical design, we
have software design, we have product design, whatever.
But if you ask yourself about what's the purpose of design,
it's usually to solve the problem.
The design is of a solution. So again, getting better at the
design, it's like getting better at modelling, but at a different
level of abstraction. Evaluate designs.
Let's say you're looking at the microphone I'm using right now.
What about it's design? Is it good or bad?
This mute button which is impossible to reach, is it good?
Does it solve the problem or should I keep my app open on the
screen all the time? Probably that says something
about the design. And once you're will get into
that notion of design and again design of whatever from
appliances to software underneath, usually there are
the same principles that are driving that design being good
or bad. And usually there will be some
representation of distance and knowledge in software design.
As I said, we have a integration strength and distance and
balance and graphical design. For example, you have sizes of
components on the web page. The greater the distance, the
bigger should be the size, right?
The greater the the distance that your mouse travels.
So yeah, these are the topics that are sort of philosophical,
but underneath they will definitely make you a much
better software architect, software designer, or just
software engineer. That's what I call myself.
Right, I really love this meta with them, right.
So you kind of like will bring up a topic that is a quite high
level. I like the term model of model
actually. So it explains about the
contract really, really well, right?
So you have a model and then you come up with another model to
actually exchange the knowledge or information between maybe two
components or two services, right?
So flat, it's a pleasure to actually discuss with you about
this topic. I think coupling is something
that we all software engineers really, really need to
understand well. And I find that in many software
engineers, when we talk about software design and this is less
included in the design, simply because we talk about
architecture patterns, probably technologies, right, or maybe
called technologies, Kubernetes and things like that.
But actually talking about coupling, modularity,
complexity, knowledge, boundaries and things like that
is very little in the discussion.
So I hope people are more well equipped now with the book,
right? And for people who love to maybe
ask you questions, maybe continue discussion or maybe
just find out more about this topic.
Is there a place where they can find such resources online?
Yeah, so you can find me on Twitter.
You can find me on LinkedIn. I assume the links are going to
be in the show notes. I have a blog, which looks like
a very sad place right now because, yeah, I didn't have
time to update it. And every month somebody, some
nice person, emails me saying that a link on your blog is
broken and I promise to fix it. But here we are.
So yeah, I would say Twitter and LinkedIn, these are the places
to get news. The Coupling book supposed to be
published in September 24. At the moment it's available on
O'Reilly online learning platform.
That draft is probably the same one that's going to be
published, just improved with professional copy editing and
professional illustrations. And yeah, also there are going
to be a summary chapter, but that's about it.
Right, So I really highly recommend the book, right?
So I think you can also check it out on O'Reilly.
I read it as part of this preparation, right?
So I think it's pretty much robust and complete, I would
say, in terms of content. So yeah, I think good luck with
your publication. So hopefully people can get to
understand about this concept much better.
So thanks again for your time flat.
Thank you so much, Henry. It's been a pleasure.