androidengineers.Book a session

App Modularization

Why Modularize? Boundaries & Ownership

article25 minMedium

A single-module Android app works fine up to ~50,000 lines of code or ~10 engineers. Beyond that, build times balloon, ownership becomes unclear, and any change anywhere could break anything. Modularization is the engineering response to that scaling problem.

The Problems Modularization Solves

Slow builds: In a monorepo, a change to a utility function in utils/ rebuilds the entire app. In a modular app, only modules that depend on :utils rebuild.

Monolith:        change → rebuild entire 200k LoC app → 3 min
Modular:         change → rebuild :utils + :feature-feed → 25 sec

Merge conflicts: When everyone edits files in the same module, merge conflicts are constant. Module boundaries create natural ownership lines.

Unclear ownership: "Who's responsible for this?" In a monolith, the answer is often "everyone." In a modular app, :feature-payment has an owner.

Accidental coupling: Without enforced boundaries, any class can import any other. Over time, a tightly coupled "big ball of mud" emerges.

Types of Modules

The standard convention:

:app                    ← Application module (wires everything)
:feature:feed           ← Feed feature (user-facing screen)
:feature:profile        ← Profile feature (user-facing screen)
:feature:auth           ← Auth feature (login/signup)
:data:articles          ← Article data (Repository, Room, API)
:data:users             ← User data
:domain:articles        ← Use cases, domain models
:core:ui                ← Shared UI components (design system)
:core:network           ← OkHttp, Retrofit setup
:core:database          ← Room database setup
:core:common            ← Utilities, extensions

Dependency Rules

The dependency graph must be a DAG (Directed Acyclic Graph) — no cycles:

:app → :feature:* → :domain:* → :data:* → :core:*

Rules:
- :feature modules can depend on :domain and :core
- :domain modules can depend on :core
- :data modules can depend on :core
- :core modules depend on nothing else internal
- No :feature → :feature dependencies (communicate via navigation)
- No :data → :domain (domain defines interfaces; data implements them)

Enforcing Boundaries

Kotlin visibility: Mark internal implementations internal so they don't leak across module boundaries:

// In :data:articles module
// ✅ Public API — what other modules can use
class ArticleRepository internal constructor(/* ... */)
fun provideArticleRepository(...): ArticleRepository  // exported via DI

// ✅ Internal implementation — invisible outside the module
internal class ArticleRemoteDataSource(private val api: ArticleApi) { ... }
internal class ArticleLocalDataSource(private val dao: ArticleDao) { ... }

API modules: Create a -api sub-module with only interfaces, and an -impl module with the implementation. Features depend on the -api module only.

Ownership Model

:feature:feed       → Team: Feeds
:feature:messaging  → Team: Messaging
:data:articles      → Team: Content Platform
:core:ui            → Team: Design System
:core:network       → Team: Platform

Clear ownership means:

  • PRs have clear reviewers
  • Breaking changes require coordination across team boundaries
  • Each team can move at their own speed

Build Speed: Before vs After

Real numbers from a medium-sized app (after modularization):

ScenarioBeforeAfter
Full clean build4:303:00 (modules can parallelize)
Change in :feature:feed only4:300:45
Change in :core:common4:302:30 (many dependents)
Run unit tests for :feature:feed2:000:20

Key Takeaways

  • Modularization pays off when: build times > 60 sec, team > 5 engineers, or test cycles are slow
  • Feature modules own user-facing screens; data modules own repositories; core modules provide shared infrastructure
  • Enforce with internal visibility and clear dependency rules
  • Build speed improvement comes from Gradle's incremental compilation and parallelism
  • Ownership clarity is often the most valuable benefit — not build speed

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Why Modularize? Boundaries & Ownership | Android System Design | Android Engineers