Back to Blog
Architecture

Clean Architecture in Mobile: Theory vs Reality

March 5, 202410 min read

The Promise

Clean Architecture, as described by Robert C. Martin, promises a codebase that is:

  • Independent of frameworks
  • Testable without UI or database
  • Independent of the database
  • Independent of any external agency

After 12 years of applying these principles across dozens of mobile projects — from small MVPs to large enterprise apps — I have strong opinions about when this works and when it doesn't.

When Clean Architecture Shines

Large Teams (5+ Developers)

At Jio Health, we have multiple developers working on the same app simultaneously. Clean Architecture gives us clear boundaries:

  • The **domain layer** defines business rules. It doesn't import UIKit, Flutter, or any framework.
  • The **data layer** implements repositories and data sources. One developer can swap the API implementation without touching the UI.
  • The **presentation layer** handles UI logic. A developer can redesign a screen without understanding the data pipeline.

This separation lets people work in parallel with minimal merge conflicts and clear ownership.

Long-lived Projects

Our patient app at Jio Health has been in active development since 2019. Over 5 years, we've:

  • Migrated from Objective-C to Swift
  • Added a Flutter module for new features
  • Switched from REST to gRPC for certain services
  • Replaced Realm with Core Data in some modules

Each of these changes was contained within a single layer, thanks to Clean Architecture. The domain layer — our business rules — hasn't changed.

Apps with Complex Business Logic

Healthcare has complex rules: appointment booking with timezone handling, insurance verification, prescription validation, video call scheduling. Putting these rules in a framework-independent domain layer means we can test them exhaustively with unit tests — no simulators, no mocks of UIKit classes.

When Clean Architecture is Overkill

Small Apps or MVPs

If you're building an MVP to validate an idea, Clean Architecture will slow you down. I learned this the hard way with ShipDauRoi, my startup:

The first version had 5 screens: login, map, order list, order detail, and profile. I spent 2 weeks setting up Clean Architecture layers before writing a single feature. I should have spent those 2 weeks shipping.

Rule of thumb: If your app has fewer than 10 screens and 1 to 2 developers, start with simple MVVM. You can refactor later if the app grows.

CRUD Apps

If your app is primarily fetching data from an API and displaying it, you don't need a domain layer with use cases. A repository pattern with MVVM is sufficient.

Solo Developer Projects

The overhead of maintaining separate layers, protocols, and dependency injection isn't worth it when you're the only developer. You already understand the entire codebase. The abstractions serve no communication purpose.

Common Mistakes I've Seen (and Made)

Over-abstracting Everything

I've seen codebases where every class has a protocol, every protocol has one implementation, and every method call goes through 3 layers of indirection. This makes the code harder to read, not easier.

My approach now: Only create an abstraction when you have or foresee multiple implementations. A `UserRepository` protocol makes sense if you have a `RemoteUserRepository` and a `MockUserRepository`. A `UserNameFormatter` protocol with one implementation is just noise.

Use Cases That Do Nothing

A common pattern:

class GetUserUseCase {
  let repository: UserRepository
  func execute(id: String) -> User {
    return repository.getUser(id: id)
  }
}

This use case adds no value. It's a pass-through. Either add real business logic to it or call the repository directly.

When use cases add value: When they coordinate multiple repositories, apply business rules, or transform data in meaningful ways.

Ignoring the Framework

Clean Architecture says "be independent of frameworks." Some developers take this literally and avoid framework features entirely. This leads to reimplementing things the framework already provides.

In iOS, Core Data and SwiftUI have opinions about architecture. Fighting them creates friction. Instead, isolate framework dependencies at the boundaries and let the inner layers be framework-free.

My Practical Architecture

After years of iteration, here's what I actually use:

For Large Projects (5+ devs, 20+ screens)

  • Domain layer: — Entities, use cases, repository protocols. No framework imports.
  • Data layer: — Repository implementations, API services, local storage. Framework dependencies allowed.
  • Presentation layer: — ViewModels/BLoCs, Views/Widgets. Framework-heavy.
  • Dependency injection: — Constructor injection, no DI frameworks. A simple composition root is enough.

For Medium Projects (2 to 4 devs, 10 to 20 screens)

  • MVVM with repository pattern. — No separate domain layer.
  • Protocols for repositories — (to enable testing).
  • Simple dependency injection — via factory methods.

For Small Projects / MVPs

  • MVVM or MVC. — Keep it simple.
  • No abstractions — until you feel the pain.
  • Ship fast, — refactor when the product proves itself.

Key Takeaways

  • Architecture should match your project's complexity. — Don't apply enterprise patterns to an MVP.
  • Abstractions have a cost. — Every protocol, every layer, every indirection adds cognitive overhead. Make sure it's worth it.
  • The domain layer is the most valuable part. — If you adopt only one thing from Clean Architecture, make it a framework-independent domain layer.
  • Refactoring is not failure. — Starting simple and adding architecture as complexity grows is a valid strategy.
  • Test your business logic, not your architecture. — If your tests break every time you refactor, your architecture is coupling to the wrong things.

Architecture is a tool, not a goal. Use it to serve your team and your product, not the other way around.