What is Clean Architecture and how have you applied it?
What Clean Architecture actually is
Uncle Bob’s Clean Architecture is fundamentally about the Dependency Rule: source code dependencies can only point inward, toward higher-level policy, never outward toward low-level detail.
Concentric circles: Entities (innermost) → Use Cases → Interface Adapters → Frameworks & Drivers (outermost).
The mechanism that enforces the rule: dependency inversion via protocols
protocol UserRepository {
func fetchUser(id: String) async throws -> User
}
final class FetchUserUseCase {
private let repository: UserRepository
init(repository: UserRepository) { self.repository = repository }
func execute(id: String) async throws -> User {
let user = try await repository.fetchUser(id: id)
return user
}
}
final class NetworkUserRepository: UserRepository {
func fetchUser(id: String) async throws -> User { ... }
}
Why this matters — the concrete payoffs
- Testability of business logic in total isolation
- Framework independence
- Isolating volatility
The realistic cost, stated honestly
- Genuine ceremony for simple screens
- iOS apps rarely swap frameworks/databases in practice
- Most “Clean Architecture on iOS” ends up as Clean-flavored MVVM
The shape of a strong “how I’ve applied it” answer
“On a project with genuinely complex eligibility rules — pricing logic that depended on user tier, region, and stacked promotional rules — we pulled that logic into a dedicated Use Case layer specifically because it needed to be reused across three different screens and because the rules changed frequently. We didn’t go full Clean Architecture with a Presenter layer and Gateway abstractions everywhere — that felt like ceremony we didn’t need for a team of five — but we did enforce the Repository-protocol-defined-inward pattern for that specific pricing logic, since the ROI was clear. For a simpler CRUD-style screen elsewhere in the same app, we just used plain MVVM.”
One-liner if pressed: “Clean Architecture’s core idea is the Dependency Rule — inner layers never depend on outer layers; outer layers implement protocols the inner layers define, so business logic stays framework-agnostic and testable in isolation. I’d apply it selectively — for genuinely complex or widely-reused business logic — rather than as a default for every screen.”