Architektur-Review: Wie sicher kann sich deine App verändern?
Gute Architektur zeigt sich, wenn Änderungen sicher bleiben. Lerne, wie du Abwägungen, Testbarkeit und Evolution in Android-Projekten bewertest.
Wenn du sechs Monate an einer App arbeitest und dann eine neue Anforderung kommt, zeigt sich, wie gut deine Architektur wirklich ist. Nicht der erste Build zählt, sondern ob du Änderungen sicher und schnell durchführen kannst, ohne unerwartete Seiteneffekte zu produzieren. Architecture Mastery Review ist der Schritt, bei dem du inne hältst und dein aktuelles Design kritisch bewertest – entlang der drei Achsen Tradeoffs, Testbarkeit und Evolution.
Was ist das?
Architecture Mastery Review bezeichnet den Prozess, die eigene App-Architektur systematisch daraufhin zu prüfen, wie gut sie Veränderungen standhält. Google empfiehlt für Android eine strikte Schichttrennung in UI-Layer, Domain-Layer und Data-Layer. Jede Schicht trägt eine klar abgegrenzte Verantwortung: Die UI reagiert auf State, der aus dem ViewModel kommt; das ViewModel orchestriert Use Cases oder Repositories; Repositories kapseln alle Datenquellen, egal ob Datenbank oder Netzwerk. Dieser Aufbau ist kein Selbstzweck – er sorgt dafür, dass du jeden Layer unabhängig testen, ersetzen oder weiterentwickeln kannst.
Der Begriff „Mastery” meint nicht, dass ein bestimmtes Pattern perfekt umgesetzt ist. Er meint, dass du die Konsequenzen deiner Architekturentscheidungen verstehst und begründen kannst. Warum hältst du State in einem StateFlow statt in LiveData? Warum greift das ViewModel nie direkt auf den Room-DAO zu? Diese Fragen bewusst beantworten zu können, ist der Unterschied zwischen jemandem, der eine Architektur kopiert, und jemandem, der sie beherrscht. Erst wer Tradeoffs benennt, kann beim nächsten Projekt die richtige Abkürzung nehmen – oder bewusst auf sie verzichten.
Wie funktioniert es?
Der zentrale Maßstab ist Änderbarkeit. Eine Architektur ist gut, wenn du eine Komponente austauschen kannst, ohne den Rest der App zu destabilisieren. Das erreichst du über zwei Hebel: klare Abhängigkeitsrichtungen und echte Testbarkeit.
Abhängigkeitsrichtungen bedeuten, dass innere Layer nichts von äußeren Layer wissen. Der Data-Layer importiert keine ViewModel-Klassen; der UI-Layer ruft keine Room-DAOs direkt auf. Die offizielle Architecture-Empfehlung schreibt vor, dass Abhängigkeiten immer von außen nach innen zeigen – in Richtung der stabilsten Schicht. Wenn du in einem Composable ein import androidx.room.* siehst, ist das ein starkes Warnsignal: Die UI hat gerade Wissen über die Datenbankimplementierung, das ihr nicht zusteht.
Testbarkeit ist kein nachträgliches Add-on, sondern ein Frühindikator für Designprobleme. Wenn ein ViewModel schwer zu testen ist, liegt das meistens daran, dass es zu viele Verantwortlichkeiten trägt oder direkte Abhängigkeiten auf Android-Framework-Klassen hält. Die Android Testing Fundamentals empfehlen eine Testpyramide: viele schnelle Unit Tests, wenige mittlere Integrationstests und sehr wenige E2E-Tests. Unit Tests sollen ohne Emulator laufen und in Millisekunden fertig sein.
Tradeoffs sind bewusste Entscheidungen. Eine strenge Clean-Architecture-Implementierung mit eigenen Entities pro Layer reduziert Kopplung, erhöht aber den Mapping-Aufwand erheblich. Weniger Schichten bedeuten schnellere Iteration, aber höheres Risiko bei späteren Änderungen. Beide Szenarien können richtig sein – solange du dir bewusst bist, warum du dich entschieden hast, und diese Entscheidung nicht aus Bequemlichkeit gefallen ist.
In der Praxis
Betrachte ein konkretes Szenario: Du hast ein ProfileViewModel, das direkt UserRepository aufruft, welches intern Room und einen Retrofit-Service kombiniert. Dein Unit-Test für das ViewModel muss jetzt Room-Datenbanken faken oder Retrofit-Responses mocken. Das ist aufwendig, fehleranfällig und testet am Ende mehr die Infrastruktur als deine eigentliche Logik.
// Problematisch: ViewModel ist an konkrete Implementierung gebunden
class ProfileViewModel(
private val userRepository: UserRepositoryImpl
) : ViewModel() {
val profile = userRepository.getUserProfile().stateIn(
viewModelScope, SharingStarted.WhileSubscribed(5_000), null
)
}
// Besser: Interface-basierte Abstraktion ermöglicht echte Isolation
interface UserRepository {
fun getUserProfile(): Flow<UserProfile?>
}
class ProfileViewModel(
private val userRepository: UserRepository
) : ViewModel() {
val profile = userRepository.getUserProfile().stateIn(
viewModelScope, SharingStarted.WhileSubscribed(5_000), null
)
}
// Im Test: FakeRepository statt echtem Room oder Netzwerk
class FakeUserRepository : UserRepository {
override fun getUserProfile(): Flow<UserProfile?> =
flowOf(UserProfile(name = "Testnutzer", id = 42))
}
Mit diesem Interface kannst du im Unit-Test eine FakeUserRepository injizieren. Kein Emulator, kein Room-Setup, keine Netzwerklatenz. Der Test läuft in unter einer Millisekunde und gibt dir unmittelbares Feedback über deine ViewModel-Logik.
Typische Stolperfalle: Entwickler fügen ein Interface ein, nutzen Dependency Injection aber nicht konsequent. Das Interface existiert, aber ViewModel und Repository sind im gesamten App-Code immer noch direkt verdrahtet – zum Beispiel über val repo = UserRepositoryImpl(db, api) direkt im ViewModel-Konstruktor statt über Hilt oder eine Factory. Das Interface verpufft ohne Wirkung. Prüfe im Code-Review, ob jede Abhängigkeit tatsächlich über ein Interface injiziert wird und nicht als konkrete Klasse instanziiert.
Für Evolution gilt: Wenn du später von Room auf SQLDelight oder von Retrofit auf Ktor wechseln möchtest, darf das ViewModel davon nichts mitbekommen. Der Umbau bleibt dann auf den Data-Layer beschränkt, und deine vorhandene Test-Suite schlägt sofort an, falls sich das Verhalten ändert. Genau das ist der Wert einer sauber entkoppelten Architektur: Nicht weniger Arbeit beim ersten Feature, sondern deutlich weniger Risiko beim zwanzigsten.
Fazit
Architecture Mastery Review ist keine einmalige Checkliste am Projektende, sondern eine kontinuierliche Haltung: Du bewertest regelmäßig, ob deine Schichten sauber getrennt sind, ob neue Features die Abhängigkeitsrichtungen respektieren, und ob deine Unit-Test-Suite noch schnell Feedback gibt. Öffne jetzt dein aktuelles Projekt und suche nach einer Klasse, die drei oder mehr Verantwortlichkeiten trägt. Versuche, einen Unit-Test dafür zu schreiben, ohne Android-Framework-Klassen zu mocken. Wenn das unmöglich erscheint, hast du deinen nächsten Refactoring-Ansatzpunkt gefunden – und damit den konkreten Beweis, dass Architecture Mastery Review kein theoretisches Konzept ist, sondern ein praktisches Werkzeug für bessere Apps.