Android Coden
Android 4 min lesen

Der Zweck von Software-Architektur

Gute Architektur macht Android-Apps wartbar und testbar. Du lernst, warum Struktur Komplexität dauerhaft unter Kontrolle hält.

Wenn du eine Android-App von Grund auf neu baust, denkst du zuerst an Features. Architektur klingt nach unnötigem Overhead — bis das Projekt nach sechs Monaten kaum noch wartbar ist und jede neue Anforderung drei unerwartete Bugs produziert. Gute Architektur ist kein Selbstzweck, sondern das Fundament, auf dem Wartbarkeit, Testbarkeit und die Fähigkeit zur kontrollierten Änderung erst entstehen.

Was ist das?

Architektur bezeichnet die grundlegenden Entscheidungen darüber, wie ein Softwaresystem in Teile zerlegt ist, wie diese Teile miteinander kommunizieren und welche Abhängigkeiten zwischen ihnen erlaubt sind. Im Android-Kontext meint das konkret: Welcher Code darf auf die Datenbank zugreifen? Welche Schicht kennt das ViewModel — und welche bewusst nicht? Welche Klassen sind voneinander entkoppelt, damit sie unabhängig verändert werden können?

Der übergeordnete Zweck dieser Struktur ist Komplexitätskontrolle. Jede App wächst. Jeder wachsende Codestand wird komplexer. Ohne bewusste Architektur degeneriert dieses Wachstum zu einem dicht verwobenen Geflecht aus Abhängigkeiten, in dem jede Änderung unvorhergesehene Nebenwirkungen hat. Architektur schafft Grenzen, die verhindern, dass Komplexität unkontrolliert eskaliert — und diese Grenzen sind es, die eine App über Monate und Jahre hinweg handhabbar halten.

Wichtig ist das mentale Modell: Architektur ist keine einmalige Designentscheidung am Projektbeginn, sondern eine fortlaufende Disziplin. Du triffst bei jedem Commit eine architektonische Entscheidung, bewusst oder unbewusst.

Wie funktioniert es?

Das offizielle Android-Architekturmodell empfiehlt eine strikte Schichtentrennung in drei Ebenen, die jeweils klar definierte Verantwortlichkeiten tragen.

UI-Schicht — Hier leben Composables, Activities, Fragments und ViewModels. Diese Schicht kennt den App-Zustand und rendert die Oberfläche, enthält aber keine Business-Logik. ViewModels halten UI-State und delegieren Datenoperationen nach unten.

Domain-Schicht (optional, aber empfohlen ab mittlerer Komplexität) — Use Cases kapseln komplexe Geschäftsregeln und koordinieren mehrere Repositories. Diese Schicht kennt weder UI-Frameworks noch Datenbankdetails und ist damit vollständig plattformunabhängig und einfach testbar.

Data-Schicht — Repositories abstrahieren den Datenzugriff. Ob Daten aus einer Room-Datenbank, einer REST-API oder einem lokalen Cache kommen, interessiert die darüberliegenden Schichten nicht. Diese Abstraktion ist der Schlüssel zu austauschbaren Implementierungen.

Jede Schicht kommuniziert ausschließlich mit der direkt darunter liegenden Schicht. Eine Composable ruft kein Repository direkt auf; ein Repository kennt kein ViewModel. Diese Einwegabhängigkeit ist der entscheidende Mechanismus: Sie macht jede Schicht unabhängig testbar und austauschbar.

Testbarkeit ist dabei kein Bonus-Feature, sondern ein direktes Ergebnis guter Architekturarbeit. Wenn dein ViewModel kein Android-Framework kennt, kannst du es in einem reinen JUnit-Test ohne Emulator prüfen. Wenn dein Repository ein Interface implementiert, kannst du in Tests eine Fake-Implementierung injizieren, ohne echte Netzwerkaufrufe auszulösen. Schlechte Architektur macht genau das unmöglich — und damit macht sie auch das Testen zum Albtraum.

In der Praxis

Stell dir eine Compose-App vor, die eine Artikelliste von einem Server lädt. Die schnelle Lösung ist ein direkter API-Aufruf im Composable:

// Antipattern: Netzwerkabruf direkt in der Composable
@Composable
fun ArticleList() {
    val articles = remember { mutableStateOf<List<Article>>(emptyList()) }
    LaunchedEffect(Unit) {
        articles.value = RetrofitClient.api.getArticles()
    }
    LazyColumn {
        items(articles.value) { article -> ArticleItem(article) }
    }
}

Das funktioniert zunächst. Aber dieses Pattern hat gravierende Folgen: Die Composable ist nicht isoliert testbar, weil sie direkt von einem echten Netzwerk-Client abhängt. Jede Änderung am API-Schema erzwingt Änderungen in der UI-Schicht. Und bei einer Rotation des Geräts wird ein neuer Netzwerkabruf ausgelöst, weil kein ViewModel den State überlebt.

Die sauberere Lösung trennt die Schichten klar:

// ViewModel hält den State und delegiert an das Repository
class ArticleViewModel(
    private val repository: ArticleRepository
) : ViewModel() {
    val articles: StateFlow<List<Article>> = repository
        .getArticles()
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
}

// Repository-Interface erlaubt Fake-Implementierungen im Test
interface ArticleRepository {
    fun getArticles(): Flow<List<Article>>
}

class ArticleRepositoryImpl(
    private val api: ArticleApi
) : ArticleRepository {
    override fun getArticles(): Flow<List<Article>> = flow {
        emit(api.getArticles())
    }
}

Die Composable kennt jetzt nur noch das ViewModel. Das ViewModel kennt nur das Repository-Interface. Im Test injizierst du eine FakeArticleRepository-Implementierung und prüfst das ViewModel vollständig ohne Netzwerk oder Android-Runtime.

Typische Stolperfalle: ViewModels werden über Zeit zu groß, weil Business-Logik direkt dort landet statt in Use Cases. Das Warnsignal ist ein ViewModel, das mehrere Repositories koordiniert und dabei inhaltliche Regeln anwendet — etwa „zeige nur Artikel, die seit weniger als 7 Tagen veröffentlicht wurden und mindestens drei Tags haben”. Sobald das passiert, gehört diese Regel in einen Use Case, der unabhängig vom ViewModel testbar ist.

Fazit

Architektur ist der Hebel, mit dem du Änderungen in deiner App auf einzelne, überschaubare Teile begrenzt. Wer heute eine saubere Schichtentrennung einhält, zahlt beim nächsten Refactoring und beim Schreiben von Tests einen Bruchteil des Aufwands, den eine unstrukturierte App verursacht. Prüfe deinen eigenen Code aktiv: Ruft eine Composable direkt ein Repository auf? Hat dein ViewModel mehr als 300 Zeilen? Gibt es Klassen, die gleichzeitig Datenbankzugriff, Business-Logik und UI-State verwalten? Diese Warnsignale sind keine theoretischen Verstöße gegen Clean-Code-Prinzipien — sie sind die konkreten Stellen, an denen deine nächste Änderungsanforderung unnötig lange dauern wird.

Quellen (5)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.