DIP (Dependency Inversion Principle) — modüller arasındaki bağımlılıkların oluşturulma kurallarını tanımlayan SOLID'in beşinci ilkesidir: üst düzey modüller alt düzey modüllere bağımlı olmamalı, her ikisi de soyutlamalara bağımlı olmalıdır. Soyutlamalar ayrıntılara bağımlı olmamalı — ayrıntılar soyutlamalara bağımlı olmalıdır. Robert Martin tarafından Clean Architecture (2017)'de açıklanan bu ilke, gevşek bağlı mimarinin temelini oluşturur. Bu kitaba göre, bağımlılıkların tersine çevrilmesi ilkesi uygulama katmanları arasındaki katı bağları ortadan kaldırır.
Önemli Noktalar
DIP (Dependency Inversion Principle), modüller arasındaki bağımlılık yönüne ilişkin geleneksel görüşü tersine çeviren bağımlılıkların tersine çevrilmesi ilkesidir. Üst düzey modüller (iş mantığı) doğrudan alt düzey modüllere (veritabanı, ağ, UI) bağımlı olmamalıdır. Bunun yerine, her iki düzey de üst düzey modülde tanımlanan soyutlamalara bağımlıdır.
DIP'nin resmi formülasyonu iki kural içerir: A — üst düzey modüller alt düzey modüllere bağımlı olmamalı, her ikisi de soyutlamalara bağımlı olmalıdır. B — soyutlamalar ayrıntılara bağımlı olmamalı, ayrıntılar soyutlamalara bağımlı olmalıdır. İkinci kural birinciden türetilir: eğer bir soyutlama ayrıntılara bağımlıysa, üst düzey bir modül için istikrarlı bir temel olamaz.
DIP olmadan, tipik bir mimari şöyle görünür: BusinessLogic → DatabaseRepository — iş mantığı doğrudan somut bir depoya bağımlıdır. DIP ile: BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic, DatabaseRepository'nin varlığını bilmez, yalnızca iş mantığının dışında uygulanan DatabaseService arayüzünü bilir.
Tersine çevirme, kontrol akışı ve bağımlılık akışının zıt yönlerde gitmesi anlamına gelir. Kontrol akışı yukarıdan aşağıya gider: UI → ViewModel → UseCase → Repository. Bağımlılık akışı aşağıdan yukarıya gider: Repository, UseCase'de tanımlanan bir arayüzü uygular. Repository (alt düzey), UseCase'ye (üst düzey) bağımlıdır.
Bu tersine çevirme, DIP ile sıradan katman ayırımı arasındaki temel farktır. Geleneksel katmanlı mimaride, her katman altındaki katmana bağımlıdır. DIP'li mimaride, tüm katmanlar soyutlamalara bağımlıdır, bu soyutlamaların uygulaması ise altyapı katmanında bulunur ve DI mekanizmaları aracılığıyla üst katmanlara “bağlanır”.
DIP mekanizması, üst düzey modüllerde soyutlamalar tanımlanarak ve alt düzey modüllerde bunların uygulanmasıyla gerçekleştirilir. Üst düzey modül, ihtiyaç duyduğu işlevsellik için bir arayüz bildirir. Alt düzey modül bu arayüzü uygular. Kablolama (wiring), uygulamanın kompozisyon kökünde gerçekleşir.
Mevcut koda DIP ekleme süreci: alt düzey modül için bir arayüz çıkarın, bu arayüzü üst düzey modüle (veya ayrı bir soyutlama katmanına) taşıyın, üst düzey modülün bağımlılığını arayüzü kullanacak şekilde yeniden yazın, alt düzey modülün bu arayüzü uygulamasını sağlayın. Bu adımlardan sonra, bağımlılık yönü tersine çevrilmiştir.
DIP, bir kompozisyon kökü mekanizması gerektirir — uygulamada tüm bağımlılıkların oluşturulduğu ve birbirine bağlandığı bir nokta. Android'de bu, Application.get() veya Hilt bileşenidir, iOS'te — AppDelegate veya SceneDelegate. Kompozisyon kökü, kodun somut uygulamaları bildiği tek yerdir.
DIP, uygulama katmanları arasında mimari sınırlar oluşturur. ViewModel, UserRepository arayüzüne bağımlı olduğunda, sunum ve domain katmanları arasında bir sınır oluşur: ViewModel (sunum), verilerin nereden geldiğini bilmez. Bu sınır, ViewModel'i etkilemeden UserRepository uygulamasının (Room → REST → Mock) değiştirilmesine izin verir. Bu tür sınırlar ne kadar çoksa, uygulama çerçeve ve kütüphane değişikliklerine karşı o kadar dirençlidir.
Google tarafından önerilen Android mimarisinde DIP, domain katmanında bulunan ve Repository arayüzlerine bağımlı olan UseCase'ler aracılığıyla uygulanır. RepositoryImpl, veri katmanındadır ve bu arayüzleri uygular. Sunum katmanı (ViewModel), UseCase'lere bağımlıdır. Bağımlılık yönü sunumdan domaine, domainden veriye gider — ancak hiçbir katman diğer bir katmanın somut uygulamalarını bilmez.
DIP ve DI sıklıkla karıştırılır, ancak farklı kavramlardır. DIP mimari bir ilkedir (NE yapılmalı: soyutlamalara bağımlı olmak). DI bir uygulama modelidir (NASIL yapılmalı: bağımlılıkları kurucu üzerinden iletmek). DIP “modüller neye dayanmalı?” sorusunu yanıtlar, DI ise “nesneler bağımlılıklarını nasıl alır?” sorusunu yanıtlar.
Dependency Injection, bir nesneye kurucu, yöntem veya özellik aracılığıyla bağımlılıkları enjekte etmenin bir yoludur. Bir Kotlin sınıfı, kurucusu aracılığıyla bir Repository arayüzü aldığında — bu DI'dir. Bir ViewModel sınıfının somut bir RoomRepository uygulaması yerine Repository arayüzüne bağımlı olması — bu DIP'dir. DI araçtır, DIP hedeftir.
DI çerçevesi olmadan DIP'i takip edebilirsiniz: kompozisyon kökünde bağımlılıkların manuel olarak kablolanması da DI'dir (manuel DI). DIP'i ihlal ederek bir DI çerçevesi (Dagger, Hilt, Koin) kullanabilirsiniz: ViewModel doğrudan new() aracılığıyla bir Repository nesnesi oluşturursa — çerçeve kurulu olsa bile DIP ihlal edilmiştir. DIP mimari bir karardır, DI teknik bir ayrıntıdır.
Veri katmanına DIP uygulamasının bir Android örneğine bakalım. DIP olmadan, bir ViewModel doğrudan RoomDatabase ve DAO oluşturur. DIP ile — ViewModel, UserRepository arayüzüne bağımlıdır ve somut RoomUserRepository uygulaması dışarıdan sağlanır.
// Soyutlama domain katmanına (üst düzey) aittir
interface UserRepository {
fun getUser(id: Int): User
}
// Domain katmanı yalnızca soyutlamaya bağımlıdır
class GetUserUseCase(
private val repo: UserRepository
) {
fun execute(id: Int): User = repo.getUser(id)
}
// Veri katmanındaki uygulama, domain katmanı soyutlamasına bağımlıdır
class RoomUserRepository(
private val dao: UserDao
) : UserRepository {
override fun getUser(id: Int): User {
return dao.getById(id)
}
}
// Kompozisyon kökü
class AppModule {
fun provideUserRepository(dao: UserDao): UserRepository {
return RoomUserRepository(dao)
}
}
Bir Application Coordinator ve navigasyon protokolü ile iOS örneği:
// Domain katmanında navigasyon soyutlaması
protocol AuthNavigation {
func navigateToHome()
func navigateToLogin()
}
// ViewModel soyutlamaya bağımlıdır, UIKit'e değil
final class AuthViewModel {
private let navigation: AuthNavigation
init(navigation: AuthNavigation) {
self.navigation = navigation
}
func onLoginSuccess() {
navigation.navigateToHome()
}
}
// Coordinator (UIKit katmanı) domain katmanı protokolünü uygular
final class AppCoordinator: AuthNavigation {
func navigateToHome() {
// UIKit navigasyon kodu
}
func navigateToLogin() {
// UIKit navigasyon kodu
}
}
Kilit nokta: AuthViewModel (domain), AppCoordinator'ın (UIKit) varlığını bilmez. Yalnızca AuthNavigation protokolünü bilir. Yarın UIKit, SwiftUI ile değiştirilirse — AuthViewModel değişiklik gerektirmez. DIP, domain katmanını UI çerçevelerinden ve kütüphanelerinden bağımsız hale getirir.
Hilt, Android için standart DI aracıdır ve Google tarafından önerilir. Jetpack'e entegredir, ViewModel, Fragment, Service ve diğer Android bileşenlerini destekler. Hilt, @Module, @Provides, @Inject ek açıklamaları aracılığıyla kompozisyon kökü oluşturmayı otomatikleştirir. Hilt kullanmak DIP uyumluluğunu garanti etmez — UserRepository arayüzü veri katmanında değil, domain katmanında tanımlanmalıdır.
Koin, kod oluşturma veya ek açıklama işleme olmadan Kotlin için hafif bir DI çerçevesidir. Koin'in DSL'si (module, single, factory) öğrenmesi daha kolaydır, ancak bağımlılık denetimi derleme zamanında değil çalışma zamanında gerçekleşir. Koin, iOS desteği sayesinde çok platformlu projelerde (KMP) popülerdir.
Dagger 2, Hilt'in öncülüdür ve hala büyük projelerde kullanılmaktadır. Dagger, derleme zamanında DI kodu üretir ve maksimum performans ve derleme zamanında hata teşhisi sağlar. Hilt, Dagger üzerine inşa edilmiştir ve basitleştirilmiş bir API sağlar. Yeni projeler için Google, Hilt'i birincil DI çerçevesi olarak önerir.
DI modülleri mimari katmanlara karşılık gelmeli ve DomainModule, DataModule, PresentationModule olarak ayrılmalıdır. DomainModule yalnızca soyutlamalar ve UseCase'ler sağlar. DataModule, soyutlamalar için uygulamalar sağlar. PresentationModule, ViewModel'leri UseCase'lere bağlar. Bu düzenleme, domain katmanının altyapı kütüphanelerinden bağımsız kalmasını sağlar.
DI çerçeveleri arasında geçiş yaparken (örneğin, Koin'den Hilt'e), DomainModule'un yapısı değişmez — yalnızca DataModule ve PresentationModule'daki kablolama yöntemleri değişir. DIP, domain mantığının izolasyonunu sağlarken, DI çerçevesi teknik bir kablolama mekanizmasıdır.
Sıkça Sorulan Sorular
DIP, mimari sınırlarda gereklidir — uygulama katmanları arasında (domain → veri, sunum → domain). Tek bir katman içinde DIP aşırı olabilir. Örneğin, domain katmanı içindeki bir yardımcı sınıf StringFormatter, değiştirilmesi için bir neden yoksa bir arayüz gerektirmez.
Hayır. DIP bir ilkedir: modüller soyutlamalara bağımlı olmalıdır. DI bir modeldir: bir nesne bağımlılıklarını kendisi oluşturmak yerine dışarıdan alır. DI, DIP'i uygulamanın bir yoludur, ancak DIP, DI olmadan da (fabrikalar veya hizmet bulucu aracılığıyla) takip edilebilir. DI olmadan DIP mümkündür ancak mimari değeri yoktur.
Arayüzler, onları kullanan modüle aittir, onları uygulayan modüle değil. UserRepository, domain katmanında bildirilir ve veri katmanında uygulanır. Bu, DIP'in temel kuralıdır: soyutlamanın sahibi tüketicidir, uygulamanın sağlayıcısı değil.
DIP, izole katmanlarda test etmeyi mümkün kılar. UserRepository'ye (arayüz) bağımlı olan bir ViewModel, veritabanı olmadan bir mock uygulamasıyla test edilebilir. DIP olmadan, ViewModel RoomUserRepository'ye bağımlı olur ve her test için veritabanı kurulumu gerektirirdi. DIP + DI, test sırasında tam modül izolasyonu sağlar.
Hilt, Android projeleri için standart seçimdir ve Google tarafından önerilir. Koin, Kotlin Multiplatform projeleri için bir alternatiftir. Dagger 2, Hilt'e geçişin haklı olmadığı mevcut projeler içindir. Çerçeve seçimi, mimari düzeyde DIP'i takip etme gerekliliğini ortadan kaldırmaz.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun