Architecture Decision Records in Android-Projekten
ADRs halten fest, warum du Architekturentscheidungen triffst. So bleibt Android-Code langfristig nachvollziehbar.
Wenn du länger an Android-Apps arbeitest, merkst du schnell: Die eigentliche Schwierigkeit liegt nicht nur darin, Code zu schreiben. Schwieriger ist oft, Monate später noch zu verstehen, warum dein Team eine bestimmte Architektur, Bibliothek oder Teststrategie gewählt hat. Architecture Decision Records, kurz ADRs, geben dir dafür ein kleines, klares Werkzeug. Sie halten wichtige Entscheidungen so fest, dass du ihre Gründe, Alternativen und Folgen später nachvollziehen kannst.
Was ist das?
Ein Architecture Decision Record ist ein kurzer Text, der eine relevante technische Entscheidung dokumentiert. Er beantwortet nicht nur die Frage „Was haben wir gemacht?“, sondern vor allem „Warum haben wir es so gemacht?“. Genau dieser Warum-Teil ist im Alltag wertvoll, weil Git-Historie und Code-Kommentare meistens nur zeigen, dass sich etwas geändert hat. Sie erklären selten den gesamten Kontext.
In einem Android-Projekt kann ein ADR zum Beispiel festhalten, warum du eine App nach dem empfohlenen Architekturmodell mit UI-Schicht, Domain-Schicht und Data-Schicht aufbaust. Es kann erklären, warum ViewModels den UI-State bereitstellen, warum Repositorys Datenquellen kapseln oder warum Compose-State nicht direkt aus einer Datenbankklasse gelesen wird. Solche Entscheidungen wirken auf viele Dateien, Tests und spätere Features. Deshalb verdienen sie mehr Sichtbarkeit als eine kurze Notiz in einem Pull Request.
Das mentale Modell ist simpel: Ein ADR ist ein Belegstück für eine Entscheidung. Es ist kein Tutorial, keine Spezifikation für jedes Detail und kein Ersatz für sauberen Code. Es ist eher ein technisches Gedächtnis. Du beschreibst den Kontext, die Entscheidung, verworfene Alternativen und die Konsequenzen. Damit kann eine neue Entwicklerin, ein Junior-Dev oder auch dein zukünftiges Ich verstehen, welche Tradeoffs damals akzeptiert wurden.
Wichtig ist dabei das Wort „Tradeoff“. Architekturentscheidungen sind selten absolut richtig oder falsch. Du wählst zwischen Optionen mit unterschiedlichen Kosten. Eine Lösung kann testbarer sein, aber mehr Boilerplate erzeugen. Eine andere kann schneller einzubauen sein, aber später schwerer zu warten. Ein ADR macht diese Abwägung sichtbar. Dadurch wird Architektur weniger zu einer Geschmacksfrage und mehr zu einer nachvollziehbaren technischen Entscheidung.
Wie funktioniert es?
Ein ADR folgt meistens einer festen, kurzen Struktur. Du brauchst kein großes Dokumentationssystem. Viele Teams legen Markdown-Dateien in ein Verzeichnis wie docs/adr/. Jede Datei bekommt eine Nummer, einen Titel und einen Status. Typische Statuswerte sind Proposed, Accepted, Deprecated oder Superseded. So bleibt erkennbar, ob eine Entscheidung aktuell gilt oder durch eine spätere Entscheidung ersetzt wurde.
Eine nützliche Struktur besteht aus fünf Teilen. Erstens beschreibst du den Kontext: Welches Problem liegt vor? Welche Constraints gibt es? Zweitens formulierst du die Entscheidung: Was wird konkret gemacht? Drittens nennst du Alternativen: Was wurde geprüft, aber nicht gewählt? Viertens erklärst du Konsequenzen: Welche Vorteile, Kosten und Risiken entstehen? Fünftens hältst du den Status und das Datum fest. Mehr brauchst du oft nicht.
Im Android-Kontext passt dieses Vorgehen gut zu modernen Architekturleitlinien. Wenn du dich an eine klare Trennung von UI und Daten hältst, ViewModels für UI-Logik nutzt, Datenströme über Kotlin Flow bereitstellst oder Compose als UI-Toolkit einsetzt, triffst du Entscheidungen mit langfristiger Wirkung. Ein ADR kann dokumentieren, wie diese Entscheidungen mit Testbarkeit, Wartbarkeit und App-Qualität zusammenhängen.
Nimm als Beispiel State-Handling in Compose. Du könntest entscheiden, dass Screen-State aus einem ViewModel als StateFlow kommt und in der Compose-UI mit collectAsStateWithLifecycle() gesammelt wird. Diese Entscheidung hat konkrete Folgen. Die UI bleibt näher an der empfohlenen Lifecycle-Nutzung. State wird leichter testbar, weil du ViewModel-Tests schreiben kannst. Gleichzeitig müssen Entwicklerinnen und Entwickler verstehen, wann State im ViewModel liegt und wann lokaler Compose-State reicht. Genau diese Grenze kann ein ADR sauber festhalten.
ADRs helfen auch bei Testing und CI. Wenn dein Team entscheidet, dass kritische ViewModels Unit-Tests bekommen und UI-Flows zusätzlich in instrumentierten Tests geprüft werden, ist das mehr als eine technische Kleinigkeit. Es beeinflusst die Entwicklungszeit, die Pull-Request-Regeln und die Qualität vor Releases. Ein ADR kann begründen, warum diese Tests in der Continuous Integration laufen, welche Testarten Pflicht sind und welche Kosten akzeptiert werden.
Ein gutes ADR bleibt dabei bewusst knapp. Du willst kein langes Essay schreiben, das niemand liest. Ein bis zwei Bildschirmseiten reichen oft. Der Wert entsteht nicht durch Länge, sondern durch Präzision. Schreibe so, dass jemand ohne damalige Meetings den Gedankengang verstehen kann. Vermeide vage Sätze wie „Wir nutzen Pattern X, weil es besser ist“. Besser ist: „Wir nutzen Repositorys zwischen ViewModel und Datenquellen, weil ViewModels dadurch keine Details über Retrofit, Room oder Cache-Strategien kennen müssen.“
In der Praxis
Stell dir vor, du baust eine Lern-App mit Jetpack Compose. Die App zeigt Lektionen, speichert Fortschritt lokal und synchronisiert später mit einem Backend. Am Anfang könntest du versucht sein, direkt aus Composables auf eine Room-DAO oder eine Retrofit-API zuzugreifen. Das wirkt zunächst schnell. Sobald Tests, Offline-Verhalten und Fehlerzustände dazukommen, wird die UI aber schwer zu kontrollieren.
Ein ADR könnte dann so aussehen:
# ADR-004: Screen-State wird über ViewModels bereitgestellt
Status: Accepted
Datum: 2026-04-25
Kontext:
Unsere Compose-Screens zeigen Daten aus lokalen und entfernten Quellen.
Die UI soll testbar bleiben und keine Details über Room oder Retrofit kennen.
Fehler-, Lade- und Leerzustände müssen konsistent dargestellt werden.
Entscheidung:
Jeder komplexe Screen erhält ein ViewModel.
Das ViewModel stellt einen unveränderlichen UiState über StateFlow bereit.
Composables sammeln diesen State lifecycle-aware und senden Nutzeraktionen
als Methodenaufrufe an das ViewModel.
Alternativen:
1. Direkter Zugriff auf Repositorys aus Composables.
2. Lokaler Compose-State für alle Screen-Daten.
3. Globale Singleton-State-Halter.
Konsequenzen:
Die UI bleibt schlanker und besser testbar.
ViewModels enthalten mehr Koordinationslogik.
Für sehr kleine Screens darf lokaler State weiter ausreichen.
Der passende Kotlin-Code im Projekt könnte so aussehen:
data class LessonUiState(
val isLoading: Boolean = false,
val title: String = "",
val errorMessage: String? = null
)
class LessonViewModel(
private val repository: LessonRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(LessonUiState(isLoading = true))
val uiState: StateFlow<LessonUiState> = _uiState.asStateFlow()
fun loadLesson(id: String) {
viewModelScope.launch {
_uiState.value = LessonUiState(isLoading = true)
runCatching { repository.getLesson(id) }
.onSuccess { lesson ->
_uiState.value = LessonUiState(title = lesson.title)
}
.onFailure {
_uiState.value = LessonUiState(
errorMessage = "Lektion konnte nicht geladen werden"
)
}
}
}
}
@Composable
fun LessonScreen(
viewModel: LessonViewModel,
lessonId: String
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(lessonId) {
viewModel.loadLesson(lessonId)
}
when {
state.isLoading -> CircularProgressIndicator()
state.errorMessage != null -> Text(state.errorMessage)
else -> Text(state.title)
}
}
Das ADR erklärt nicht jede Zeile dieses Codes. Es erklärt die Architekturgrenze, die hinter dem Code steht. Dadurch wird ein Pull Request leichter zu prüfen. Reviewer müssen nicht jedes Mal neu diskutieren, ob ein Composable direkt die Datenquelle kennen darf. Sie können auf das ADR verweisen und prüfen, ob der neue Code zur akzeptierten Entscheidung passt.
Eine gute Entscheidungsregel lautet: Schreibe ein ADR, wenn eine Entscheidung mehrere Features, mehrere Entwickler oder spätere Releases betrifft. Du brauchst kein ADR für jede kleine Hilfsfunktion und nicht für jede Umbenennung. Sinnvoll ist es bei Themen wie App-Architektur, Modulstruktur, Persistenz, Netzwerkbibliothek, Navigation, State-Management, Fehlerbehandlung, Teststrategie, CI-Pipeline oder Release-Qualitätskriterien.
Eine typische Stolperfalle ist, ADRs als nachträgliche Rechtfertigung zu schreiben. Dann steht dort nur, was bereits gebaut wurde, aber nicht, welche Alternativen wirklich diskutiert wurden. So verliert das Dokument an Wert. Schreibe das ADR möglichst nah am Entscheidungszeitpunkt. Es darf kurz sein und später ergänzt werden. Wichtiger ist, dass die Abwägung ehrlich bleibt: Welche Nachteile nehmt ihr bewusst in Kauf?
Eine zweite Stolperfalle ist zu viel Allgemeinheit. „Wir nutzen moderne Android-Architektur“ ist keine Entscheidung. Konkreter wäre: „Feature-ViewModels greifen nur über Repository-Interfaces auf Daten zu; Implementierungen liegen in der Data-Schicht.“ Das lässt sich im Code-Review prüfen. Du kannst sehen, ob ein ViewModel plötzlich direkt eine Retrofit-Service-Klasse verwendet. Du kannst auch Tests daran ausrichten, weil das ViewModel gegen ein Fake-Repository getestet werden kann.
Auch veraltete ADRs brauchen Pflege. Wenn dein Team später von einer alten Architektur auf eine neue Struktur wechselt, solltest du das alte ADR nicht löschen. Markiere es als Superseded und verweise auf das neue ADR. So bleibt die Historie erhalten. Gerade in gewachsenen Android-Apps ist diese Historie wichtig, weil ältere Module oft anders aussehen als neue Module. Ohne Dokumentation wirkt das schnell chaotisch. Mit ADRs erkennst du, welche Unterschiede historisch gewachsen sind und welche bewusst weiter gelten.
Für Lernende ist ein ADR außerdem ein gutes Trainingswerkzeug. Wenn du eine Beispiel-App baust, schreibe zu einer einzigen Entscheidung ein kleines ADR. Begründe zum Beispiel, warum dein ViewModel einen UiState nutzt, warum du Repositorys einführst oder warum bestimmte Tests in der CI laufen sollen. Danach prüfst du im Code, ob deine Entscheidung wirklich umgesetzt ist. Diese Rückkopplung zwischen Entscheidung, Code und Test schärft dein Architekturverständnis.
Fazit
Architecture Decision Records helfen dir, die Geschichte deiner Android-App lesbar zu halten. Sie dokumentieren nicht nur die gewählte Lösung, sondern auch den Kontext, die Tradeoffs und die Konsequenzen. Dadurch werden Architektur, Qualität, Testing und Release-Praxis besser überprüfbar. Übe das mit einer kleinen App: Wähle eine echte Entscheidung, schreibe ein kurzes ADR, prüfe den Code im Review dagegen und ergänze einen Test, der die wichtigste Annahme absichert.