Public API Minimization: Nur das Nötige nach außen
Lerne, wie du die öffentliche Schnittstelle deiner Android-Module klein hältst. Weniger API bedeutet weniger Abhängigkeiten und mehr Flexibilität.
Jedes Gradle-Modul in deiner App ist eine eigenständige Einheit mit eigener Logik – und je kleiner seine nach außen sichtbare Oberfläche ist, desto leichter lässt sich dieses Modul intern weiterentwickeln, ohne andere Teile der App zu brechen. Public API Minimization ist das Prinzip, genau das sicherzustellen: Zeige nach außen nur, was andere Module wirklich brauchen.
Was ist das?
Public API Minimization bedeutet, die Menge der öffentlichen Typen, Funktionen und Eigenschaften eines Moduls auf das absolute Minimum zu beschränken. Alles, was andere Module nicht direkt verwenden müssen, bleibt internal, private oder protected. Das Gegenteil – wahllos public deklarieren, weil es gerade bequem ist – erzeugt einen unsichtbaren Vertrag: Jede öffentliche Klasse verspricht stillschweigend, so zu bleiben, wie sie ist. Änderst du sie später, brichst du potenziell alle Konsumenten.
Im Android-Kontext betrifft das vor allem Projekte mit mehreren Gradle-Modulen. Die offizielle Architekturempfehlung von Google beschreibt solche Multi-Module-Setups als bevorzugte Struktur für skalierbare Apps. Feature-Module, Data-Layer-Module und Domain-Layer-Module kommunizieren über klar definierte Schnittstellen miteinander. Je kleiner und präziser diese Schnittstellen sind, desto unabhängiger können sich die Module weiterentwickeln. Ein internes Refactoring im :data:user-Modul sollte das :feature:profile-Modul nicht interessieren.
Wie funktioniert es?
Kotlin bietet vier Sichtbarkeitsmodifikatoren: public (Standard, wenn nichts angegeben), internal, protected und private. In einem Multi-Module-Projekt ist internal der wichtigste Hebel.
public– sichtbar für jeden, der das Modul als Abhängigkeit einbindet. Jedepublic-Deklaration ist Teil deines Vertrags.internal– sichtbar nur innerhalb desselben Gradle-Moduls. Perfekt für Implementierungsdetails, die nach außen nichts verloren haben.private– sichtbar nur innerhalb derselben Datei oder Klasse. Für rein lokale Hilfsfunktionen.protected– sichtbar für Unterklassen; in Kotlin nur innerhalb von Klassen zulässig.
Eine bewährte Strategie sieht so aus: Du definierst ein Interface im Modul und stellst es als public bereit. Die Implementierung, die dieses Interface umsetzt, markierst du als internal. Konsumenten sehen nur den Typ, nicht die konkrete Umsetzung.
Das ist besonders im Data Layer wertvoll. Die offizielle Android Data Layer-Dokumentation empfiehlt, Repositories als Interfaces zu formulieren, sodass der Rest der App gegen den Vertrag, nicht gegen eine konkrete Klasse codiert. Die tatsächliche Implementierung – mit Datenbankzugriff, Netzwerklogik und Cache-Strategie – kann jederzeit ausgetauscht werden, ohne dass irgendjemand außerhalb des Moduls davon weiß.
In der Praxis
Nehmen wir ein konkretes Beispiel: ein :data:user-Modul, das Benutzerdaten bereitstellt.
// Öffentliche API des Moduls – das ist alles, was andere Module sehen
interface UserRepository {
suspend fun getUser(id: String): User
}
data class User(val id: String, val name: String)
// Interne Implementierung – nicht nach außen sichtbar
internal class UserRepositoryImpl(
private val api: UserApiService,
private val dao: UserDao
) : UserRepository {
override suspend fun getUser(id: String): User {
return dao.getUser(id) ?: api.fetchUser(id).also { dao.insertUser(it) }
}
}
Andere Module kennen nur UserRepository und User. Ob die Daten aus einer lokalen Datenbank, einem REST-Endpunkt oder einem In-Memory-Cache kommen, ist ihr Problem nicht. Du kannst UserRepositoryImpl morgen komplett umschreiben – kein konsumierendes Modul bemerkt es, solange der Vertrag des Interfaces gilt.
Die häufigste Stolperfalle
Du fügst in der Hektik schneller Feature-Entwicklung eine neue Hilfsklasse hinzu und lässt den Sichtbarkeitsmodifikator weg. In Kotlin ist der Standardwert public – die Klasse wird sofort Teil deiner API. Das passiert erstaunlich oft, und wenn eine andere Stelle im Projekt sie erst einmal verwendet, ist das Rückgängigmachen aufwendig.
Ein effektives Gegenmittel: Vereinbare im Team eine Code-Review-Regel der Form „Alle neuen Typen beginnen als internal, bis ein konkreter Konsument sie außerhalb des Moduls braucht.” Viele Teams ergänzen diese Regel durch einen Lint-Check, der warnt, wenn eine neue public-Deklaration ohne Begründung im Review auftaucht. Damit verschiebt sich die Standardannahme: nicht public bis zum Beweis des Gegenteils, sondern internal bis zum Beweis der Notwendigkeit.
Schnittstellen statt Klassen exponieren
Wenn du nicht umhinkommst, einen Typ öffentlich zu machen, prüfe zuerst, ob ein Interface ausreicht. Das schützt Konsumenten vor zukünftigen Konstruktor-Änderungen und macht Unit-Tests im konsumierenden Modul einfacher, weil das Interface problemlos gemockt werden kann. Eine konkrete Klasse als öffentlicher Typ zwingt Konsumenten dagegen dazu, mit jedem neuen Konstruktorparameter mitzuwachsen.
Fazit
Public API Minimization zahlt sich aus, sobald dein Projekt über ein einzelnes Modul hinauswächst. Nimm dir eines deiner bestehenden Module und gehe jede public-Deklaration durch: Wird sie wirklich von außen aufgerufen? Falls nicht, ändere sie zu internal, führe einen Build durch und prüfe, welche Compile-Fehler auftauchen – das zeigt dir präzise, wo echte Abhängigkeiten existieren und wo public nur aus Gewohnheit steht. Diese Übung ist die schnellste Art, ein realistisches Gefühl für die tatsächliche API-Fläche deines Codes zu entwickeln und gleichzeitig die Grundlage für langfristig wartbares Multi-Module-Design zu legen.