Software design
The structure that mirrors the business: modularity, cohesion and coupling
Why modularize only by technical affinity, when business elements are the true protagonists of the application?
Cristiano Rego Barros
6 min read
Originally published on Medium
These days, with concepts such as DDD (Domain-Driven Design), microservices and Clean Architecture more popular than ever, there is a lot of talk about modularity. Yet some questions keep coming up around it, such as:
- What does modularity really mean?
- Is modularity purely technical?
- How can I build a modular application?
These questions often go unexplained, and we need to dig into them to understand how to achieve it. As Glenford Myers put it back in 1978:
“Ninety-five percent of the words [about software architecture] are spent extolling the benefits of ‘modularity’ and little, if anything, is said about how to achieve it.”
— Glenford J. Myers (1978)
So, to help with this, I try to explain the topic a little in this short article.
What is modularity
The dictionary defines modularity as the quality of being modular, or made up of modules. Eric Evans, author of Domain-Driven Design, states that modules are consolidated design elements that should have low coupling with each other and high internal cohesion.
Cohesion
Cohesion can be understood as how strongly the internal elements of a module relate to each other. In other words, a cohesive module groups components that make sense together because they work toward the same goal or responsibility. The more related these components are, the more cohesive the module.
The image below shows an illustrative graph with three groups:
- Cloud: elements 1 to 5;
- Star: elements 6 to 8;
- Element 9 on its own.
There are many ways one could group or modularize the nodes in the graph above, for example:
- Even and odd nodes;
- Nodes with and without relationships, and so on.
However, since no context is given, we can say that, visually, the grouping described above is the one that produces modules with the most connected items — that is, the most cohesive arrangement.
This search for the best reasons (or best characteristics) to group design elements is what gives your modules value and makes them cohesive.
Coupling
Coupling, in turn, can be described as the level of dependency between different items.
So elements that are highly coupled to each other have a strong dependency between them. It follows that loosely coupled elements are those in which independence is promoted.
Looking at the illustration below, we can see that the elements of Module A have no relationship or dependency with elements of the other modules. On the other hand, the items in B are frequently related to and dependent on the element in C, creating coupling between modules.
These frequent associations can make the items in B dependent on — even hostage to — changes caused by the items in C. In that case, the modules start to become fragile, since they are not meeting their own needs. That is why we aim for decoupled, independent modules: they are strong modules.
Within a cohesive module, coupling between its items becomes more acceptable, since those elements are connected and work toward the same goal — that is, changes to one piece of the module are very likely to be valid for the others.
On the other hand, when you couple to items in non-cohesive modules or across different modules, changes to one item are very likely not relevant to the others, which can cause problems in those relationships. This shows why it matters to aim for cohesive, decoupled modules and, likewise, to steer clear of coupled, disconnected ones.
Layered modularity
As Richards and Ford put it:
“Software systems model complex systems, which tend toward entropy (or disorder).”
— Mark Richards and Neal Ford
From this principle, it is clear how important it is to always strive for a clear, cohesive organization in software systems, so the project stays maintainable and evolvable — unlike the casual tendency in which the codebase’s arrangement gets in the way of the system’s evolution.
In modern software, it is common to split components and artifacts by their technical differences. This gives rise to layered architecture, in which a system’s internal components are organized by technical responsibility, creating layers such as:
- Presentation layer (controllers, consumers);
- Application layer (use cases, service classes);
- Infrastructure layer (database mapping, connection management with external drivers).
But are these groups of components (or modules) really cohesive and decoupled? Is this way of splitting enough for a clean, clear codebase?
At first glance, this split seems to make sense, since the elements share a common technical responsibility. However, when you adopt layers, it is common to end up in the scenario shown below, where each layer has different elements doing the same tasks, but for different domains.
Imagine there are strict boundaries between domains, so elements of one business domain don’t talk to elements of another and only relate to items within their own context. The relationships between elements and components in the application would look like the image below:
As a result, there is a large volume of communication between modules and none within them. That’s because elements that perform a given task don’t (or shouldn’t) relate to others of the same kind: a pencil doesn’t relate to another pencil, but to paper, or to the hand of whoever is writing. Their relationships are defined by their business purpose — for example, the elements needed for someone to write a book.
Domain-based modularity
As we saw in the previous section, the casual approach of modularizing by technical layers is not the best choice and can even become a trap in some contexts. This is where modularization by business domain comes in, strongly influenced by concepts from DDD (Domain-Driven Design).
Essentially, the idea behind this kind of modularity is to split the application’s components by their business capabilities, resulting in cohesive, decoupled modules. A representation of this concept looks like the image below:
This strategy works very well for early-stage projects aiming at a microservices architecture. In that architecture, one of the biggest difficulties and most common questions is the right granularity for services. At the start of a project, when little is known about the domain, a domain-modularized architecture can be an excellent option.
The project can start with a few services, or even a single one, split internally by domain. As each domain becomes reasonably robust, the whole module can be extracted and moved into another service, in another codebase, without affecting or being affected by the others — since it now follows its own independent lifecycle.
Moreover, nothing prevents each domain module from being organized internally by technical capabilities. This gives you a hybrid structure: organized in layers, but modularized by business domain — something like the image below:
This approach not only promotes real cohesion and decoupling, but also paves the way for an architecture more closely aligned with the business. To go deeper into this kind of modular organization, I recommend studying Domain-Driven Design (DDD), which covers identifying and separating domains strategically in much more depth.
Conclusion
In short, modularity goes far beyond splitting code into technical layers. It is about aligning structure and purpose, making sure the software’s organization reflects the business logic it represents. Adopting domain-oriented modularization is a solid step toward systems that are more cohesive, more scalable and cheaper to maintain over time.
If you have faced challenges with modularity in your projects or have different experiences organizing your codebase, let me know. Let’s keep this conversation going and share what we learn about making our applications more sustainable and better aligned with the business.