No topic in mobile software engineering is more controversial, and more discussed, than architecture.
From MVC to MVVM, to VIPER to MV, to whatever the hell this is, everybody has an opinion, and everybody else is wrong. Discourse about screen-level architecture, however, is a misguided use of energy.
Architectural discussions should be about how your modules fit together, the trade-offs you’re making, and how your team is structured. Building great software is as much about human problems as it is about technology problems.

Today, we’ll journey through the lifecycle of a scaling app to understand the problems solved by modular architecture. We’ll discuss strengths and weaknesses of each approach, as well as the organisational structure for which they are best suited.
The Lifecycle of a Scaling App
You create your Android Studio or Xcode project, eyes gleaming and keen to drag your nascent MVP into the cold light of day. Perhaps you’re an indie dev, a newly-hired mobile lead, or the senior on a greenfield project.
You’re busy iterating and proving out the concept in these early, lightning-fast release cycles. No time to stop and think about architecture — you don’t even know if the project will survive the quarter.
You start to see promise with early users, revenue trickles in, and your feature set gets more complex. Build time ticks upwards, but that doesn’t stop you shipping.
With great success comes great responsibility. More engineers are assigned to your team as your app grows into a flagship product.
More lines of code, longer build times, blurred lines of responsibility. More requests from marketing and product managers. Tech debt inexorably accumulates with each release. Coordination overhead compounds with build time to strain the system you’ve built.
You pitch the business case for splitting the app up into modules. You convince the CTO, or your co-founders, or the project sponsor, that a temporary pause on feature development will accelerate your engineers and pay off quickly.
Your project is approved; and your inner circle of engineers begins to evaluate options. Which architecture works best for your business?
Core Module Architecture
After single-module apps, this is the simplest architecture. There are just two parts:
An
Appmodule which contains the top-level project and features.A
Coremodule which contains everything else. This might include domain models, networking, persistence, authentication, utilities, analytics, and shared UI components.
If you’re unfamiliar with modular architecture, this approach helps you dip your toes into interface design and access control. Creating a Core module will also introduce you to the technical steps required to create a module and integrate it using your package manager or build system.
The killer, *ahem*, app for this architecture is multi-target apps. This is a common pattern for marketplace startups, such as Uber (which have separate apps for drivers and riders) or AirBnb (which has apps for both hosts and guests). The Core module can easily share logic and UI between the two targets. In my first foray into entrepreneurship, Patcher, we used this approach to build apps for both mechanics and car owners.
The Core module architecture is a natural fit for Kotlin Multiplatform projects, where UI code is native but business logic is written in a cross-platform Kotlin library.
Late-stage team structures often spin up a mobile platform team alongside the product teams, a team structure which leads to clear division of responsibilities and straightforward lines of communication when using the Core module architecture.
Splitting off core business logic and libraries into a separate module will quickly yield build-time benefits, since feature-level changes won’t need to re-compile the Core module. For larger projects, the Core module approach often serves as a transitionary step towards more complex forms of modularisation.
Layered Modular Architecture
This approach is a tiered architecture, analagous to the layered monolith pattern common in web servers. The core ideas of the Layered approach are to group common functionality together as layers, based on the data requirements of the layer above. This helps enforce a unidirectonal data flow through each layer.

The benefits of layered architecture are simplicity and consistency.
All networking code lives in the networking layer, all the data access work is done in the data layer, and all the features live together in the UI layer.
The number of modules, and the function of each layer, are strictly defined to avoid descending into dependency-graph hell. Top-level UI features are simple to develop since data is served up in a consistent manner.
The data layer interface enforces an abstraction: what, not how. The UI layer doesn’t usually know how the data is being retrieved, be it from the network, from local persistence, or from a cache in memory.
This is clear in Bev, my trusty open-sauce project: the top-level Bev module imports a Repository module — the data access layer. This in turn synthesises data from both the Database and Networking modules to find the most up-to-date data, as fast as possible.
This layered approach is a great option for medium-complexity projects and mid-sized teams, where engineers might specialise into building out data and networking, UI, or internal libraries. This was the architecture of choice for several banking and consumer electronics apps I’ve worked on while I worked in tech consulting.
The layered logic is another good fit for Kotlin Multiplatform or multi-target apps. Platform teams and product teams will be at home working on layered projects, but issues with build times might persist if your project is fairly top-heavy with UI-layer features.





