Clean Architecture: Wann es wirklich hilft
Clean Architecture ist kein Selbstzweck. Wann Use Cases und Grenzen wirklich helfen – und wann sie nur Overhead erzeugen.
Architektur ist kein Selbstzweck. Das gilt nirgendwo so deutlich wie bei Clean Architecture: Das Konzept bietet echten Mehrwert, wenn ein Projekt an Komplexität gewinnt – aber es erzeugt unnötigen Overhead, wenn du es blindlings auf jede kleine App anwendest. In diesem Artikel lernst du, wann Use Cases, Schichtgrenzen und der Domain-Layer tatsächlich helfen – und wann du sie getrost weglassen kannst.
Was ist das?
Clean Architecture ist ein von Robert C. Martin (Uncle Bob) beschriebenes Architekturkonzept, das Code in konzentrische Schichten aufteilt. Die zentrale Idee: Abhängigkeiten zeigen immer nach innen. Die Geschäftslogik in der Mitte weiß nichts von Datenbanken, UI oder Netzwerk – nur die äußeren Schichten kennen die inneren, nie umgekehrt.
Im Android-Kontext übersetzt Google diese Idee in drei Schichten, die in der offiziellen Architekturempfehlung beschrieben sind:
- UI-Layer – Compose-Screens, ViewModels, UI-State
- Domain-Layer – Use Cases (optional, aber wertvoll bei komplexer Logik)
- Data-Layer – Repositories, Datenquellen (Room, Retrofit, DataStore)
Der Begriff Pragmatism im Titel ist entscheidend: Clean Architecture ist kein Dogma. Du musst nicht alle Schichten und Interfaces sofort einführen. Die offizielle Android-Dokumentation bezeichnet den Domain-Layer ausdrücklich als optional – und meint das ernst.
Wie funktioniert es?
Das Herzstück von Clean Architecture auf Android sind Use Cases (auch Interactors genannt). Ein Use Case ist eine Klasse mit einer einzigen öffentlichen Methode, die genau eine Geschäftsregel umsetzt. Er sitzt im Domain-Layer, hat keine Abhängigkeit zu Android-Framework-Klassen und ist damit einfach unit-testbar.
class GetLatestNewsUseCase(
private val newsRepository: NewsRepository
) {
suspend operator fun invoke(): List<NewsItem> =
newsRepository.getNews()
.filter { it.publishedAt.isAfter(LocalDate.now().minusDays(7)) }
}
Der Use Case nimmt ein Repository-Interface entgegen – nicht die konkrete Implementierung. Das ist die Boundary: Der Domain-Layer definiert das Interface, der Data-Layer implementiert es. So kann der Domain-Layer in Tests mit einer Fake-Implementierung betrieben werden, ohne echte Datenbank oder Netzwerk.
Das ViewModel konsumiert den Use Case direkt:
@HiltViewModel
class NewsViewModel @Inject constructor(
private val getLatestNews: GetLatestNewsUseCase
) : ViewModel() {
val news = flow { emit(getLatestNews()) }
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
}
Schichtgrenzen schützen in beide Richtungen: UI-Logik dringt nicht in Repositories ein, und Datenbankdetails verschwinden nicht in ViewModels. Solange diese Grenzen respektiert werden, kannst du jede Schicht unabhängig austauschen, testen und weiterentwickeln.
In der Praxis
Wann lohnt sich der Domain-Layer?
Googles Architekturempfehlungen geben eine klare Entscheidungsregel: Füge einen Domain-Layer ein, wenn mehrere ViewModels dieselbe Geschäftslogik benötigen oder wenn die Logik komplex genug ist, dass sie isoliert getestet werden soll. Bei einem einfachen Listen-Screen, der nur Daten aus einem Repository anzeigt, ist ein Use Case reine Bürokratie.
Faustformel:
- 1 ViewModel, 1 Repository, keine Transformation → kein Use Case nötig
- Komplexe Filterung, Kombination mehrerer Quellen, wiederverwendete Regel → Use Case sinnvoll
Typische Stolperfalle: Zu frühe Abstraktion
Der häufigste Fehler ist, Clean Architecture von Anfang an vollständig zu implementieren – mit Interfaces für jede Klasse, Mappern zwischen jeder Schicht und Use Cases für jeden Datenbankaufruf. Das Ergebnis: fünfmal mehr Dateien, aber kein einziger Test, der von der Komplexität profitiert.
Starte mit dem schlanken Zwei-Schichten-Modell (UI + Data). Wenn du merkst, dass ViewModels dieselbe Logik duplizieren, oder dass du einen Feature-Bereich nicht testen kannst, ohne das Netzwerk zu mocken – dann ist der richtige Zeitpunkt, einen Use Case und ein Interface einzuführen. Architektur wächst mit dem Problem.
Grenzen nicht erodieren lassen
Eine häufige Erosion in bestehenden Projekten: Das ViewModel greift direkt auf Room-DAOs zu, weil das Repository „doch eh nur weiterleitet”. Sobald du das zulässt, verlierst du die Testbarkeit und kannst die Datenquelle nicht mehr austauschen. Selbst ein Repository, das nur weiterleitet, ist kein unnötiger Overhead – es ist die Boundary, die zukünftige Änderungen isoliert und Testfakes ermöglicht. Einmal aufgeweichte Grenzen sind schwer wiederherzustellen.
Fazit
Clean Architecture auf Android ist ein Werkzeug, kein Ziel. Starte mit dem schlanken Zwei-Schichten-Modell, beobachte, wo Logik dupliziert wird oder wo Tests schwierig werden – und führe dann gezielt Use Cases und explizite Interfaces ein. Öffne jetzt ein Projekt, an dem du gerade arbeitest: Greift ein ViewModel direkt auf eine Datenquelle zu? Gibt es eine Filterregel, die in drei ViewModels kopiert wurde? Diese Signale zeigen echten Bedarf an. Wer pragmatisch vorgeht, profitiert von besserer Testbarkeit und klarerem Code – ohne sich in vorzeitiger Abstraktion zu verlieren.