Skip to content

Modularization Overview

Sk Niyaj Ali edited this page Dec 12, 2024 · 1 revision

Modularization Overview

Modularization is the practice of breaking the concept of a monolithic, one-module codebase into loosely coupled, self contained modules.

project-root/ Mobile Wallet
│
├── buildLogic/            # Shared build configuration(Gradle)
│
├── core/                  # Core business logic module
│   ├── common/            # Common code shared across platforms(KMP)
│   ├── model/             # Model classes and data structures(KMP)
│   ├── data/              # Data models and repositories(KMP)
│   ├── network/           # Networking and API clients(KMP)
│   ├── domain/            # Domain-specific logic(KMP)
│   ├── ui/                # UI components and screens(CMP)
│   ├── designsystem/      # App-wide design system(CMP)
│   └── datastore/         # Local data storage(KMP)
│
├── feature/               # CMP Feature Specific module
│   ├── accounts/          # CMP account specific module
│   ├── auth/              # CMP auth specific module
│   ├── editpassword/      # CMP editpassword specific logic
│   ├── faq/               # CMP faq specific logic
│   ├── finance/           # CMP finance specific logic
│   ├── history/           # CMP history specific logic
│   ├── home/              # CMP home specific logic
│   ├── invoices/          # CMP invoices specific logic
│   ├── kyc/               # CMP kyc specific logic
│   ├── make-transfer/     # CMP make-transfer specific logic
│   ├── merchants/         # CMP merchnats specific logic
│   ├── notification/      # CMP notification specific logic
│   ├── payments/          # CMP payments specific logic
│   ├── profile/           # CMP profile specific logic
│   ├── qr/                # CMP qr specific logic
│   ├── receipt/           # CMP receipt specific logic
│   ├── request-money/     # CMP request-money specific logic
│   ├── savedcards/        # CMP saved-cards specific logic
│   ├── send-money/        # CMP send-money specific logic
│   ├── settings/          # CMP settings specific logic
│   └── upi-setup/         # CMP upi-setup specific logic
│
├── mifospay-android/      # Android Application module
├── mifospay-ios/          # iOS Application module
├── mifospay-desktop/      # Desktop application module
├── mifospay-web/          # Web application module
│
├── mifospay-shared/       # Shared Kotlin Multiplatform code

Benefits of modularization

This offers many benefits, including:

Scalability - In a tightly coupled codebase, a single change can trigger a cascade of alterations. A properly modularized project will embrace the separation of concerns principle. This in turn empowers the contributors with more autonomy while also enforcing architectural patterns.

Enabling work in parallel - Modularization helps decrease version control conflicts and enables more efficient work in parallel for developers in larger teams.

Ownership - A module can have a dedicated owner who is responsible for maintaining the code and tests, fixing bugs, and reviewing changes.

Encapsulation - Isolated code is easier to read, understand, test and maintain.

Reduced build time - Leveraging Gradle’s parallel and incremental build can reduce build times.

Dynamic delivery - Modularization is a requirement for Play Feature Delivery which allows certain features of your app to be delivered conditionally or downloaded on demand.

Reusability - Proper modularization enables opportunities for code sharing and building multiple apps, across different platforms, from the same foundation.

Modularization pitfalls

However, modularization is a pattern that can be misused, and there are some gotchas to be aware of when modularizing an app:

Too many modules - each module has an overhead that comes in the form of increased complexity of the build configuration. This can cause Gradle sync times to increase, and incurs an ongoing maintenance cost. In addition, adding more modules increases the complexity of the project’s Gradle setup, when compared to a single monolithic module. This can be mitigated by making use of convention plugins, to extract reusable and composable build configuration into type-safe Kotlin code. In our project, these convention plugins can be found in the build-logic folder.

Not enough modules - conversely if your modules are few, large and tightly coupled, you end up with yet another monolith. This means you lose some benefits of modularization. If your module is bloated and has no single, well defined purpose, you should consider splitting it.

Too complex - there is no silver bullet here. In fact it doesn’t always make sense to modularize your project. A dominating factor is the size and relative complexity of the codebase. If your project is not expected to grow beyond a certain threshold, the scalability and build time gains won’t apply.

Modularization strategy

It’s important to note that there is no single modularization strategy that fits all projects. However, there are general guidelines that can be followed to ensure you maximize its benefits and minimize its downsides.

A barebone module is simply a directory with a Gradle build script inside. Usually though, a module will consist of one or more source sets and possibly a collection of resources or assets. Modules can be built and tested independently. Due to Gradle's flexibility there are few constraints as to how you can organize your project. In general, you should strive for low coupling and high cohesion.

  • Low coupling - Modules should be as independent as possible from one another, so that changes to one module have zero or minimal impact on other modules. They should not possess knowledge of the inner workings of other modules.

  • High cohesion - A module should comprise a collection of code that acts as a system. It should have clearly defined responsibilities and stay within boundaries of certain domain knowledge. For example, the core:network module in our project is responsible for making network requests, handling responses from a remote data source, and supplying data to other modules.

Types of modules

---
config:
  layout: elk
---
flowchart TD
    n2[":core:data"] --> n9[":core:network"] & n1[":core:common"] & n3[":core:model"]
    n9 --> n3
    n14["mifospay-shared"] --> n12[":feature:home"] & n9 & n2
    n15["mifospay-android"] --> n14
    n16["mifospay-ios"] --> n14
    n18["mifospay-web"] --> n14
    n17["mifospay-desktop"] --> n14
    n4[":core:ui"] --> n3
    n12 --> n2 & n1 & n9 & n4 & n3
    n2@{ shape: rounded}
    n9@{ shape: rounded}
    n1@{ shape: rounded}
    n3@{ shape: rounded}
    n14@{ shape: rounded}
    n12@{ shape: rounded}
    n15@{ shape: rounded}
    n16@{ shape: rounded}
    n18@{ shape: rounded}
    n17@{ shape: rounded}
    n4@{ shape: rounded}
    style n2 fill:#2962FF,color:#FFFFFF
    style n9 fill:#2962FF,color:#FFFFFF
    style n1 fill:#2962FF,color:#FFFFFF
    style n3 fill:#2962FF,color:#FFFFFF
    style n14 fill:#FF6D00,color:#FFFFFF
    style n12 fill:#00C853,color:#FFFFFF
    style n15 fill:#D50000,color:#FFFFFF
    style n16 fill:#D50000,color:#FFFFFF
    style n18 fill:#D50000,color:#FFFFFF
    style n17 fill:#D50000,color:#FFFFFF
    style n4 fill:#2962FF,color:#FFFFFF

Loading

Top tip: A module graph (shown above) can be useful during modularization planning for visualizing dependencies between modules.

The Mobile Wallet app contains the following types of modules:

  • The mifospay-android module containing the Android application class and the MainActivity.

  • The mifospay-ios module containing the iOS application class.

  • The mifospay-desktop module containing the Desktop application class.

  • The mifospay-web module containing the Web application class.

  • The mifospay-shared module is the whole shared application module. It is responsible for bringing together all the other modules required for the specific app to function correctly. This includes UI scaffolding and navigation.

  • feature: modules - feature specific modules which are scoped to handle a single responsibility in the app. These modules can be reused by any app, including test or other flavoured apps, when needed, while still keeping it separated and isolated. If a class is needed only by one feature module, it should remain within that module. If not, it should be extracted into an appropriate core module. A feature module should have no dependencies on other feature modules. They only depend on the core modules that they require.

  • core: modules - common library modules containing auxiliary code and specific dependencies that need to be shared between other modules in the app. These modules can depend on other core modules, but they shouldn’t depend on feature nor app modules.

Modules

Using the above modularization strategy, the Now in Android app has the following modules:

Name Responsibilities Key classes and good examples
mifospay-shared Brings everything together required for the app to function correctly. This includes UI scaffolding and navigation. MifosPayApp, MifosPayViewModel
App-level controlled navigation via MifosNavHost, MifosAppState, RootNavGraph etc.
feature:1,
feature:2
...
Functionality associated with a specific feature or user journey. Typically contains UI components and ViewModels which read data from other modules.
Examples include:
HomeScreen
HomeViewModel
core:data Fetching app data from multiple sources, shared by different features. ClientRepository
core:designsystem Design system which includes Core UI components (many of which are customized Material 3 components), app theme and icons. MifosIcons MifosButton MifosTheme
core:ui Composite UI components and resources used by feature modules, such as the transaction history card. Unlike the designsystem module, it is dependent on the data layer since it renders models, like Transaction item card. TransactionItemCard TransactionHistoryCard
core:common Common classes shared between modules. MifosDispatchers
DataState
core:network Making network requests and handling responses from a remote data source. KtorfitClient
core:datastore Storing persistent data using DataStore. UserPreferencesDataSource
UserPreferencesRepository
core:model Model classes used throughout the app. User
Client
Beneficiary

Modularization in Mobile Wallet

Our modularization approach was defined taking into account the “Mobile Wallet” project roadmap, upcoming work and new features. Additionally, our aim this time around was to find the right balance between overmodularizing a relatively small app and using this opportunity to showcase a modularization pattern fit for a much larger codebase, closer to real world apps in production environments.

This approach was discussed with the Android community, and evolved taking their feedback into account. With modularization however, there isn’t one right answer that makes all others wrong. Ultimately, there are many ways and approaches to modularizing an app and rarely does one approach fit all purposes, codebases and team preferences. This is why planning beforehand and taking into account all goals, problems you’re trying to solve, future work and predicting potential stepping stones are all crucial steps for defining the best fit structure under your own, unique circumstances. Developers can benefit from a brainstorming session to draw out a graph of modules and dependencies to visualize and plan this better.

Our approach is such an example - we don’t expect it to be an unchangeable structure applicable to all cases, and in fact, it could evolve and change in the future. It’s a general guideline we found to be the best fit for our project and offer it as one example you can further modify, expand and build on top of. One way of doing this would be to increase the granularity of the codebase even more. Granularity is the extent to which your codebase is composed of modules. If your data layer is small, it’s fine to keep it in a single module. But once the number of repositories and data sources starts to grow, it might be worth considering splitting them into separate modules.

We are also always open to your constructive feedback - learning from the community and exchanging ideas is one of the key elements to improving our guidance.

Note

This project followed Google recommend architecture and modularization pattern. followed the Now in Android App modularization approach and mostly content of this page are copied from it..

Demo credentials

  • Fineract Instance: venus.mifos.io
  • username: venus
  • password: Venus2023#
Clone this wiki locally