When it comes to building software, be it a website, a mobile application, or any type of computer software, one of the most noticeable problems is the difficulty to comprehend large projects with complex codebases, often involving dozens of programmers. As a developer, it is common to spend more time figuring out what the code does rather than actually writing code.
What is a modular structure?
In recent years, fast-growing tech companies witnessed difficulties with traditional organization based functional divisions (e.g marketing, design, development, etc.). Teams were unable to adapt such a fast-changing environment. Spotify was among the first to start organizing its teams into cross-functional tribes — autonomous entities responsible for managing and delivering their own products. Think of it like multiple small companies as part of a larger one. When a tribe becomes too big, it divides itself into new ones and so do the responsibilities. A key reason why companies started exploring this scheme is scalability, as they grow, they need to expand painlessly. If you are not familiar with this concept, I suggest you watch this great video about how Spotify organizes itself.
What if the same pattern gets applied to programming?
In programming the same rule can apply. An application is like a tribe and modules are the squads composing it, each having a single responsibility. A module encapsulates a set of related functions and components semantically related with its own functional responsibility. They have all the assets they need to work on their own and can be tested independently of the rest of the application.
Determining a single responsibility suggests a module should never have more than one reason to change. This means it only has one responsibility. If someone asks you to explain what your module does and you use the words “or/and/also…” then it most likely means your module violates the single responsibility principle. Keep in mind, there is no universal definition of what a module does, it’s up to you to determine the granularity of it.
“Do one thing and do it well“
— Unix philosophy
I compose my modules based on what they provide to the user. For example, if I want to create a mobile application using React-Native which provides the ability to use the device’s fingerprint scanner, I would pack together all the methods related to the fingerprint scanner. This includes the in-app popups requesting to touch the sensor for all platforms. Whether you use the fingerprint scanner to login, authorize a payment or as part of the settings section of your app, everything related to the scanner comes from a single place. This is your module.
Why you should follow this structure?
When you work on a large project, it can be a difficult to identity to origin of an issue. As a developer, you might spend valuable time digging through thousands of lines of code until you understand all the relationships. Organizing your code by modules means you start thinking around a concept where you break down your code into related pieces and provide a public interface to use your module. It helps maximize code sharing and reusability in different sections of your application and even on other projects.
Another important attribute of a module-based structure is the ability to replace one by another, as long as both modules meet the same requirements. In a world where everything moves fast and new features replace old ones in no time, breaking your code by modules will give you the freedom to build multiple versions of them to facilitate A/B testing and fully replace old ones when your team is satisfied with the result.
Applying this concept to your React applications
Creating a module means you will group a set of related components, methods and assets together, providing a public interface to be used by other modules. Just like you would create a node module.
Let’s create a module called
security for a React-Native application. This module will allow a user to use the fingerprint sensor or facial recognition on their device and fallback to a pin number whenever there is no sensor available. Here is how our module is structured.
When you render the
Authenticate component, the user is prompted to authenticate himself. This component consumes methods from
sensor.js which defines methods for enabling/disabling the sensor and for verifying its availability. If the sensor is available and set up, the
Authenticate component takes care of rendering the
SensorPopup component which uses the sensor for authentication. Otherwise, it will render the
PinPopup component which will verify the user’s pin against what has been previously saved in the storage, using methods from
user-preferences.js. Once one of the popups is done validating the user’s credentials, the
Authenticate component receives the information and triggers either
onFail methods, followed by
Our module is now ready to be used within different sections of our application.
The module will be used by other modules to authorize an access or an action. For example, it can be used by the Login module to log the user in, by the Payments module to authorize a payment, and by the Settings module to enable or disable the sensor from the app preferences. It provides a public interface, meaning it only exports a few methods to make accessible to the outside world, the
Authentication component, the
sensor methods, as well as the
user-preferences methods. The popup components are not exported as you are not supposed to use them directly.
Advanced level: the multi-package repository
Juggling a multi-module project over multiple repos is like trying to teach a newborn baby how to ride a bike.
— Sebastien McKenzie — creator of Babel, Yarn, Facebook Engineer
This means that you will end up with multiple projects within the same git repository. Many companies such as Google, Facebook or AirBnB use the single repository strategy for their projects. The often called “mono-repo” helps you coordinate changes across projects and libraries to increase your velocity and continuous modernization. Code reviews become faster as you only need a single pull request to modify multiple projects — as an atomic unit. Another great feature is share the same configuration files for all your projects, whether you use Jest, Eslint or TypeScript, which can be defined at the root level of your repository.
There are some libraries which can help you manage your repository. I personally use Lerna — it allows you to turn your codebase into packages, which can then be published to npm independently. It also provides powerful tools for versioning and running cross-package commands.
Another great tool you might want to consider is the GitHub’s Code Owners feature. With a multi-package repository it’s not always clear who owns which package and Code Owners allows you to assign ownership to specific folders. This feature requires code owners to review pull requests changing their owned code. This ensures code quality and adds an extra layer of security.
Here is a final look at how you can have multiple React-Native applications reusing the same modules in a single repository managed by Lerna. We have
3 different mobile applications, which define their own navigation, and the shared modules that they used within the same repository. And if you ever feel like your
security module is amazing you can publish it to npm.
/SellersApp <--- those are the app containers with navigation /analytics
/settings/.eslintrc <--- same lint rules
/jest.config.js <--- same jest config
Now that you’ve decided to introduce squads and tribes in your company, I hope you will also be able to negotiate some time with your manager to reorganize your projects using this modular structure.
Thinking large scale is not easy, you can’t really assume the future, how many developers will be working on your app and how to prevent them from over-engineering when it grows. A modular pattern can help you scale your application without the need to anticipate future features and evolutions that will be introduced next.
More articles from me
- How to better organize your React applications?
- What are the main differences between ReactJS and React-Native?
- The essential boilerplate to authenticate users on your React-Native app
- Level up your career as a Developer