Modular design: Deep vs. shallow modules
What are modules? Why do we use them at all? What do we apply them to? What’s the difference between deep and shallow ones? Let’s delve into the basics of modular design.
Table of contents
- What is modular design?
- What is a module?
- What is a shallow module?
- Pros and cons of shallow modules
- Deep vs. shallow modules. A summary
What is modular design?
Modular design is a design principle allowing for the minimization of dependencies.
In modular design, a system you are building is divided into partially independent parts, called modules. Combined together, they have the goal of rendering a certain result, be it a feature or a more sophisticated product.
Modular design is an excellent way of writing simple, reusable, and actionable code.
- provides superb user experience,
- supports development,
- splits complex problems into simpler tasks,
- builds robust software.
What is a module?
In modular design, every part of the system that has its own interface and implementation is called a module, e.g. an API with a REST interface, or a single class. Typically, when referring to a module we mean a certain piece of software with a shared domain e.g. context in Elixir.
A module’s interface should always hide its implementation and is usually simpler than the latter.
To use interfaces, we need 2 types of information:
- formal, i.e. all information which can be directly derived from code, e.g. types of data passed, list of access methods/functions, etc.)
- and informal, i.e. constraints in the usage of a particular module (everything you need to know, but can’t derive it directly from code, e.g. the necessity of calling a certain function before using the module).
Deep vs. shallow modules
Back to modules. There are 2 types of them:
- and deep.
What is a deep module?
A deep module is a module with a small interface hiding a big functionality beneath, for example adding something to a Ruby file.
The Ruby module has 2 parameters, i.e. the file access path and the text we want to add.
In this particular case, we only need to call one specific function and we get the desired effect.
What is a shallow module?
Shallow modules carry small functionalities beneath big interfaces.
A good example is a CRUD module written in Ruby, with all the functions/methods that make up a database record (create, read, update, delete, new).
Refactoring shallow modules to deep modules
To learn how to transform shallow modules into deep ones, let’s use the example of a subscription module.
We usually can activate, prolong, change the type or perform various payment operations relating to subscription.
For this exercise, let’s focus on activation, deactivation, prolonging, and changing subscription types (e.g. from standard to a premium user).
It’s easy to notice that both “activate” and “deactivate” functionalities are, in their essence, about changing the subscription’s state.
Therefore it makes perfect sense to combine them both into a single functionality that will handle both operations.
Combining them also eliminates the number of possible returned errors, e.g. when a user clicks the “activate” button of an already activated subscription.
This is why transforming two shallow modules into a deep one called “change state” will benefit the UX of our application.
Can we go any further with the given set of functionalities? It will be hard, as “change type” and “prolong” are totally different operations.
Well, OK, we could reduce all three (“change state”, “change type”, “prolong”) modules to a single “handle” interface, but it would hide all of our system’s complexity.
Can we do something different?
Let’s go back to the complete list of features within the subscriptions module.
If you take a look and think for a minute, you will easily notice that all of the above operations can be organized into 2 sets:
- payments actions (register payment, handle failed payment, change payment provider),
- subscriptions actions (changing subscription state, prolonging)
Data from both sets can, in turn, be arranged into logs containing information about their respective domains.
Both sets also can make up a single “calculate subscription state” module that gives us a global insight into the subscription’s state, i.e. whether it’s active or inactive, prolonged or not, etc.
Once such a system is created, it’s easy to change its whatever part.
We can, for example, delete the payments log and add a new one from another provider.
We can perform a similar operation with the subscription log, and/or enhance it with new functionalities.
Pros and cons of shallow modules
- Allow sophisticated data flow in a system,
- Allow to easily optimize performance
- Have very complex interfaces due to a large number of functionalities and informal requirements, which in turn makes them more difficult to use
- Make code more complex and less readable,
- Require guards and state checks,
- Let unexpected things happen,
- Make unit testing more complicated due to large numbers of simple functionalities
Pros and cons of deep modules
- Have simple and easy-to-use interfaces
- Are easy to unit-test
- Performing stream data testing is easier
- Simplify client’s code
- Empowers good design, as systems built upon deep modules are easier to comprehend and enhance
- Harder to figure out, require a thorough understanding of what the client wants to achieve with a system we are building,
- It can be harder to increase the performance of a system built with deep modules.
Deep vs. shallow modules. A summary
Deep modules have small interfaces and big functionality handling complex operations.
Shallow modules, on the contrary, have big interfaces and small functionality in each of the module’s functions.
REST modules creating assets are an example of shallow modules.
On the other hand, a payment gateway performing several operations on data can serve as a good example of a deep module.
Shallow modules can be transformed into deep modules, and in many cases, this operation will benefit your code.
For more information, I strongly encourage you to grab the book “A Philosophy of Software Design” by John Ousterhout. Enjoy!