DIP (Dependency Inversion Principle) — prinsip kelima SOLID yang menentukan aturan membangun ketergantungan antar modul: modul tingkat atas tidak boleh bergantung pada modul tingkat bawah, keduanya harus bergantung pada abstraksi. Abstraksi tidak boleh bergantung pada detail — detail harus bergantung pada abstraksi. Prinsip ini, yang dijelaskan oleh Robert Martin dalam Clean Architecture (2017), menjadi dasar arsitektur yang longgar terhubung. Menurut buku ini, prinsip inversi ketergantungan menghilangkan ikatan kaku antara lapisan aplikasi.
Utama
DIP (Dependency Inversion Principle) — prinsip inversi ketergantungan yang membalikkan pandangan tradisional tentang arah ketergantungan antar modul. Modul tingkat tinggi (logika bisnis) tidak boleh langsung bergantung pada modul tingkat rendah (basis data, jaringan, UI). Sebaliknya, kedua tingkat bergantung pada abstraksi yang didefinisikan di modul tingkat atas.
Rumusan formal DIP mencakup dua aturan: A — modul tingkat atas tidak boleh bergantung pada modul tingkat bawah, keduanya harus bergantung pada abstraksi. B — abstraksi tidak boleh bergantung pada detail, detail harus bergantung pada abstraksi. Aturan kedua adalah konsekuensi dari aturan pertama: jika abstraksi bergantung pada detail, ia tidak dapat menjadi dasar yang stabil untuk modul tingkat atas.
Tanpa DIP, arsitektur tipikal terlihat seperti ini: BusinessLogic → DatabaseRepository — logika bisnis langsung bergantung pada repositori konkret. Dengan DIP: BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic tidak tahu tentang keberadaan DatabaseRepository, ia hanya tahu antarmuka DatabaseService yang diimplementasikan di luar logika bisnis.
Inversi berarti aliran kontrol dan aliran ketergantungan diarahkan ke arah yang berlawanan. Aliran kontrol berjalan dari atas ke bawah: UI → ViewModel → UseCase → Repository. Aliran ketergantungan berjalan dari bawah ke atas: Repository mengimplementasikan antarmuka yang didefinisikan di UseCase. Repository (tingkat bawah) bergantung pada UseCase (tingkat atas).
Inversi ini adalah perbedaan utama antara DIP dan pembagian lapisan biasa. Dalam arsitektur berlapis tradisional, setiap lapisan bergantung pada lapisan di bawahnya. Dalam arsitektur dengan DIP, semua lapisan bergantung pada abstraksi, dan implementasi abstraksi ini berada di lapisan infrastruktur yang terhubung
ke lapisan atas melalui mekanisme DI.
Mekanisme DIP diwujudkan melalui pendefinisian abstraksi di modul tingkat atas dan implementasinya di modul tingkat bawah. Modul tingkat atas mendeklarasikan antarmuka untuk fungsionalitas yang dibutuhkannya. Modul tingkat bawah mengimplementasikan antarmuka ini. Perakitan (wiring) terjadi di tingkat akar komposisi aplikasi.
Proses penerapan DIP pada kode yang ada: ekstrak antarmuka untuk modul tingkat rendah, pindahkan antarmuka ini ke modul tingkat atas (atau ke lapisan abstraksi terpisah), tulis ulang ketergantungan modul atas ke antarmuka, paksa modul tingkat rendah untuk mengimplementasikan antarmuka ini. Setelah langkah-langkah ini, arah ketergantungan telah berubah menjadi kebalikannya.
DIP memerlukan mekanisme akar komposisi — titik dalam aplikasi di mana semua ketergantungan dibuat dan dihubungkan satu sama lain. Di Android ini adalah Application.get() atau komponen Hilt, di iOS — AppDelegate atau SceneDelegate. Akar komposisi adalah satu-satunya tempat di mana kode mengetahui implementasi konkret.
DIP membentuk batas arsitektural antar lapisan aplikasi. Ketika ViewModel bergantung pada antarmuka UserRepository, batas muncul antara lapisan presentation dan domain: ViewModel (presentation) tidak tahu dari mana data berasal. Batas ini memungkinkan perubahan implementasi UserRepository (Room → REST → Mock) tanpa memengaruhi ViewModel. Semakin banyak batas seperti ini, semakin tahan aplikasi terhadap perubahan framework dan pustaka.
Dalam arsitektur Android yang direkomendasikan Google, DIP diimplementasikan melalui UseCase yang berada di lapisan domain dan bergantung pada antarmuka Repository. RepositoryImpl berada di lapisan data dan mengimplementasikan antarmuka ini. Lapisan presentation (ViewModel) bergantung pada UseCase. Arah ketergantungan berjalan dari presentation ke domain, dari domain ke data — tetapi tidak ada lapisan yang mengetahui implementasi konkret lapisan lainnya.
DIP dan DI sering tertukar, tetapi ini adalah konsep yang berbeda. DIP — prinsip arsitektural (APA yang harus dilakukan: bergantung pada abstraksi). DI — pola implementasi (BAGAIMANA melakukannya: meneruskan ketergantungan melalui konstruktor). DIP menjawab pertanyaan pada apa modul harus didasarkan?
, DI — bagaimana objek mendapatkan ketergantungannya?
.
Dependency Injection — cara menyuntikkan ketergantungan ke dalam objek melalui konstruktor, metode, atau properti. Ketika di kelas Kotlin antarmuka Repository diteruskan melalui konstruktor — ini adalah DI. Dan fakta bahwa kelas ViewModel bergantung pada antarmuka Repository, bukan pada implementasi konkret RoomRepository — ini adalah DIP. DI — alat, DIP — tujuan.
DIP dapat dipatuhi tanpa framework DI: pengikatan manual ketergantungan di akar komposisi juga merupakan DI (manual DI). Framework DI (Dagger, Hilt, Koin) dapat digunakan sambil melanggar DIP: jika ViewModel langsung membuat objek Repository melalui new() — DIP dilanggar, bahkan jika framework telah diinstal. DIP — keputusan arsitektural, DI — detail teknis.
Mari kita lihat contoh Android penerapan DIP pada lapisan data. Tanpa DIP, ViewModel langsung membuat RoomDatabase dan DAO. Dengan DIP — ViewModel bergantung pada antarmuka UserRepository, dan implementasi konkret RoomUserRepository diberikan dari luar.
// Abstraksi milik lapisan domain (tingkat atas)
interface UserRepository {
fun getUser(id: Int): User
}
// Lapisan domain hanya bergantung pada abstraksi
class GetUserUseCase(
private val repo: UserRepository
) {
fun execute(id: Int): User = repo.getUser(id)
}
// Implementasi di lapisan data bergantung pada abstraksi lapisan domain
class RoomUserRepository(
private val dao: UserDao
) : UserRepository {
override fun getUser(id: Int): User {
return dao.getById(id)
}
}
// Akar komposisi
class AppModule {
fun provideUserRepository(dao: UserDao): UserRepository {
return RoomUserRepository(dao)
}
}
Contoh iOS dengan Application Coordinator dan protokol untuk navigasi:
// Abstraksi navigasi di lapisan domain
protocol AuthNavigation {
func navigateToHome()
func navigateToLogin()
}
// ViewModel bergantung pada abstraksi, bukan pada UIKit
final class AuthViewModel {
private let navigation: AuthNavigation
init(navigation: AuthNavigation) {
self.navigation = navigation
}
func onLoginSuccess() {
navigation.navigateToHome()
}
}
// Coordinator (lapisan UIKit) mengimplementasikan protokol lapisan domain
final class AppCoordinator: AuthNavigation {
func navigateToHome() {
// Kode navigasi UIKit
}
func navigateToLogin() {
// Kode navigasi UIKit
}
}
Poin penting: AuthViewModel (domain) tidak tahu tentang keberadaan AppCoordinator (UIKit). Ia hanya tahu protokol AuthNavigation. Jika besok UIKit diganti dengan SwiftUI — AuthViewModel tidak memerlukan perubahan. DIP membuat lapisan domain independen dari framework dan pustaka UI.
Hilt — alat DI standar untuk Android, direkomendasikan oleh Google. Terintegrasi di Jetpack, mendukung ViewModel, Fragment, Service, dan komponen Android lainnya. Hilt mengotomatiskan pembuatan akar komposisi melalui anotasi @Module, @Provides, @Inject. Penggunaan Hilt tidak menjamin kepatuhan DIP — antarmuka UserRepository harus didefinisikan di lapisan domain, bukan di lapisan data.
Koin — framework DI ringan untuk Kotlin tanpa pembuatan kode dan pemroses anotasi. DSL Koin (module, single, factory) lebih mudah dipelajari, tetapi pemeriksaan ketergantungan terjadi saat runtime, bukan saat kompilasi. Koin populer di proyek multiplatform (KMP) karena dukungan iOS.
Dagger 2 — pendahulu Hilt, masih digunakan di proyek besar. Dagger menghasilkan kode DI saat kompilasi, memberikan kinerja maksimal dan diagnostik kesalahan saat build. Hilt dibangun di atas Dagger dan menyediakan API yang disederhanakan. Untuk proyek baru, Google merekomendasikan Hilt sebagai framework DI utama.
Modul DI harus sesuai dengan lapisan arsitektural dan dibagi menjadi DomainModule, DataModule, PresentationModule. DomainModule hanya menyediakan abstraksi dan UseCase. DataModule menyediakan implementasi untuk abstraksi. PresentationModule menghubungkan ViewModel dengan UseCase. Organisasi semacam itu menjamin bahwa lapisan domain tetap independen dari pustaka infrastruktur.
Saat migrasi antar framework DI (misalnya, dari Koin ke Hilt), struktur DomainModule tidak berubah — hanya metode pengikatan di DataModule dan PresentationModule yang berubah. DIP memastikan isolasi logika domain, dan framework DI adalah mekanisme pengikatan teknis.
Pertanyaan yang Sering Diajukan
DIP diperlukan di batas arsitektural — antara lapisan aplikasi (domain → data, presentation → domain). Di dalam satu lapisan, DIP mungkin berlebihan. Misalnya, kelas utilitas StringFormatter di dalam lapisan domain tidak memerlukan antarmuka — jika tidak ada prasyarat untuk penggantiannya.
Tidak. DIP — prinsip: modul harus bergantung pada abstraksi. DI — pola: objek menerima ketergantungan dari luar, bukan menciptakannya sendiri. DI adalah cara implementasi DIP, tetapi DIP dapat dipatuhi tanpa DI (melalui pabrik atau pencari layanan). DI tanpa DIP mungkin terjadi tetapi tidak memiliki nilai arsitektural.
Antarmuka milik modul yang menggunakannya, bukan yang mengimplementasikannya. UserRepository dideklarasikan di lapisan domain dan diimplementasikan di lapisan data. Ini adalah aturan kunci DIP: pemilik abstraksi adalah konsumen, bukan penyedia implementasi.
DIP memungkinkan pengujian pada lapisan yang terisolasi. ViewModel yang bergantung pada UserRepository (antarmuka) diuji dengan implementasi mock tanpa basis data. Tanpa DIP, ViewModel akan bergantung pada RoomUserRepository dan memerlukan konfigurasi basis data untuk setiap pengujian. DIP + DI memberikan isolasi penuh modul saat pengujian.
Hilt — pilihan standar untuk proyek Android, direkomendasikan oleh Google. Koin — alternatif untuk proyek Kotlin Multiplatform. Dagger 2 — untuk proyek yang ada di mana migrasi ke Hilt tidak dapat dibenarkan. Pemilihan framework tidak menghilangkan kebutuhan untuk mematuhi DIP di tingkat arsitektur.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga