What Does Modularity Mean?

This article explores true modularity in software development, focusing on cohesion, managing dependencies, and using contracts for independence and scalability. It provides insights on organizing systems for improved flexibility, testability, and reusability, making development more efficient.

What Does Modularity Mean?

Modularity is pretty popular and trendy word. Everyone wants to work in modular project. But do you know when we can talk that project is modular?

Important
All information below is assuming that we are building a modular monolith architecture.

The existence of modules doesn't mean you have a modular project.

The separate folders or projects with names of screens in the app doesn't mean you have a modular project.

Firstly, this is just the method to group of related to each other entities, but it is still something that is very valuable. Any method of grouping dependencies is good to understand the solution better. I mean domain partitioning based on application features. Moreover, it allows introduction of a feature teams approach.

We distinguish two types of code splitting. Technical partitioning based on layers and domain one based on domain (features basically).

What is next?

Connections between modules: dependencies.

That's the core of the topic. The smaller the usage of other modules (and third-party stuff) in the module, the greater module cohesion. If you are creating a relatively simple frontend app, the topic is not complex. You will probably need to use some other modules because of navigation needs (sometimes by passing additional parameters).

The problem increases for more complex domains or backend applications.

The high number of dependencies in a particular module doesn't mean poorly designed boundaries. It happens, some module is some kind of aggregator of other modules features.

The authors Fundamentals of Software Architecture Mark Richards and Neal Ford describe what cohesion is as follows:

Cohesion refers to what extent the parts of a module should be contained within the same module. In other words, it is a measure of how related the parts are to one another. Ideally, a cohesive module is one where all the parts should be packaged together, because breaking them into smaller pieces would require coupling the parts together via calls between modules to achieve useful results.

Interactions between modules

The application uses modules to provide the user value. Interactions between modules allow the user to make something valuable. We need to support it.

How to allow one module to speak with the other one without exposing internal logic and processes?

Before I answer that question, I want to address another one first:

Why exposing to other modules the internal logic and language of the module, is wrong by design?

The answer is simple. The goal of modularisation is to make modules independent of the other one as much as it is possible. We can do that by hiding as much as possible from the outside world. By doing it, the particular feature team can develop the module logic without thinking about the consumers of the module.

To tell the truth, the team responsible for module, still have to think about the module consumers.

So, what is the answer to the first one question?

The contract

Exposing the contract of the module with its own general language, without any internal module's models, is the game changer. The idea is the following: we declare the contract that other modules consume.

If others feature teams need something specific from us, after discussion, we expose some contract (interface) constructed without using anything from internal module implementation. This way we can develop contracts and internal module logic independently.

The idea is simple. If someone wants to use our module needs to consume the contract.
In fact, the contract may not include any implementations at all.

As you can see, tje module's project implements the contract, and this makes a lot of sense. The feature team has full control over the implementation. They decide how the logic is executed. Moreover, this approach allows other modules to mock the contract and use a different one implementation. Which gives great opportunities, not only in tests.

A few benefits of using a contract approach

  • More control over implementing internal logic — less chance of needing to make changes in the module's consumers.
  • Simple changing the implementation – other modules can easily change the contract implementation, for instance, for testing purposes.
  • Less team's cognitive load – people don't need to focus on external implementations, they just think in terms of abstractions.
  • Screaming boundaries – easily visible where modules integrate each others.

Everything is possible thanks to Inversion Of Control (IoC)

Somewhere we have to define what is implemented by what. Some pretty IoC container will be very useful. Thanks to it, we can additionally introduce dependency injection to the project.

This is why we can easily (assuming the architecture is well-designed) exchange the module implementation for the mocked one.

Modularity is about organizing

Modularity is a group of a few strictly connected things. Folders, packages or projects with well named application's screens aren't modular solution. This is necessary, but not sufficient.

Modularity refers to organizing a system into logical, distinct parts (modules) that have clearly defined tasks and responsibilities. However, this "organization" has a deeper meaning and is not just about arranging code, but also about many key aspects of system design:

  • Division into smaller, independent parts: Modularity involves breaking down large, complex systems into smaller, more manageable modules. Each module performs one or several well-defined functions, allowing for better understanding and management of the code and development in Feature Teams.
  • Separation of concerns: Modularity enables a clear division of responsibilities between different components.
  • Ease of extension and modification: Thanks to modular architecture, modifying and extending the system becomes simpler. Changes in one module should not affect others, provided that boundaries (e.g., interfaces, contracts) are well defined. This means greater flexibility in project management.
  • Reusability: Modules can be reused in various contexts if they are well-designed. Properly organizing the system into modules promotes the reuse of components.
  • Testability: Modules are easier to test because they can be tested independently from the rest of the system. This facilitates unit and integration testing, improving software quality.

In summary, modularity is organization, but in a more complex way than merely tidying up code. It’s about structure, separation of responsibilities, and component independence, ultimately leading to a more understandable, scalable, and easier-to-maintain system.


~KB