Sparround

Clean Architecture və Repository pattern

Clean Architecture kodu asılılıq istiqaməti sərt qaydalara tabe olan qatlara bölür:

  • Presentation (UI + ViewModel) — istifadəçiyə görünən qat
  • Domain (use case-lər, biznes modelləri, repository interfeysləri) — framework-suz, təmiz Kotlin
  • Data (repository implementasiyaları, API/DB mənbələri, DTO-lar) — xarici dünya ilə iş

Asılılıq qaydası: oxlar həmişə içəri baxır — Data və Presentation Domain-dən asılıdır, Domain heç nədən asılı deyil. Bu, dependency inversion ilə mümkün olur: interfeys domain-də, implementasiya data-da.

Repository pattern — data mənbələrini (şəbəkə, DB, cache) tək fasad arxasında gizlədir: ViewModel "balансı ver" deyir, haradan gəldiyini bilmir. Offline-first strategiyanın qərar nöqtəsi məhz repository-dir.

Hər qatın öz modeli olur: AccountDto (data) → Account (domain) → AccountUiModel (presentation), aralarında mapper-lər.

kotlin
// DOMAIN — təmiz Kotlin, Android importu yoxdur
data class Account(val id: String, val balance: BigDecimal)

interface AccountRepository {
    suspend fun getAccounts(): List<Account>
    fun observeAccounts(): Flow<List<Account>>
}

class GetAccountsUseCase(private val repo: AccountRepository) {
    suspend operator fun invoke(): List<Account> =
        repo.getAccounts().sortedByDescending { it.balance }
}

// DATA — Retrofit/Room burada yaşayır
class AccountRepositoryImpl @Inject constructor(
    private val api: AccountApi,
    private val dao: AccountDao
) : AccountRepository {
    override suspend fun getAccounts(): List<Account> {
        val remote = api.fetchAccounts()          // List<AccountDto>
        dao.upsertAll(remote.map { it.toEntity() })
        return dao.getAll().map { it.toDomain() }  // DB — single source of truth
    }
    override fun observeAccounts() = dao.observeAll().map { list -> list.map { it.toDomain() } }
}

Qatların kodda görünüşü: interfeys domain-də, implementasiya data-da

"Use case-lər həmişə lazımdır?" mübahisəsinə hazır ol. Dogmatik cavab yox, balans gözlənilir: sadə CRUD ekranında ViewModel → Repository birbaşa əlaqəsi qəbulediləndir; use case o zaman dəyər verir ki, (1) məntiq bir neçə ViewModel-də təkrarlanır, (2) əməliyyat çoxmərhələlidir (köçürmə: limit yoxla → komissiya hesabla → təsdiq et), (3) domain qaydaları UI-dan asılı olmadan test olunmalıdır. Bank tətbiqində köçürmə axını use case üçün ideal nümunədir.

🛠 Tapşırıq

Bir feature-i üç qata böl: AccountDto (data) → Account (domain) → AccountUiModel (presentation) və aralarına mapper yaz.

  • Domain faylında *heç bir `android.` və ya Retrofit importu** olmamalıdır — yoxla.
  • Repository interfeysini domain-ə, implementasiyanı data-ya qoy.
  • Bir use case yaz (məs. GetAccountsUseCase) və onun həqiqətən dəyər verib-vermədiyini bir cümlə ilə əsaslandır.

Bitdi sayılır: "use case həmişə lazımdırmı" sualına balanslı cavabın hazırdırsa.

📚 Mənbələr və sənədlər