Clean Architecture — 2012-ci ildə Robert Martin (Uncle Bob) tərəfindən təklif edilmiş, tətbiqi müstəqil təbəqələrə ayıran çoxlaylı arxitektura: Domain (Entities, Use Cases), Data (Repositories, DataSources) və Presentation (ViewModels, Views). Əsas prinsip — Dependency Rule: asılılıqlar içəriyə yönəldilir, xarici təbəqələr daxili təbəqələrdən asılıdır, əksi yox. Clean Architecture yüksək mürəkkəblikli biznes məntiqi olan layihələr üçün mobil inkişafda tətbiq edilir. Ətraflı — The Clean Architecture kitabında.
Əsas məqamlar
Clean Architecture — 2012-ci ildə Robert Martin (Uncle Bob) tərəfindən formalaşdırılmış arxitektura nümunəsi. Əsas fikir — tətbiqin sərt asılılıq qaydası ilə təbəqələrə bölünməsi: təbəqə daxilindəki kod xaricdəki kod haqqında bilmir. Xarici təbəqələr (UI, freymvorklar, verilənlər bazası) — icra detalları. Daxili təbəqələr (biznes məntiqi, müəssisə qaydaları) — tətbiqin mahiyyəti.
Clean Architecture təbəqələri mobil inkişafda: 1) Domain — Entities (biznes obyektləri) və Use Cases (istifadə ssenariləri); 2) Data — RepositoryImpl (repozitorilərin icrası), DataSources (şəbəkə, VB, keş); 3) Presentation — ViewModels, Views (Compose/SwiftUI). Domain — asılılıqları olmayan ən daxili təbəqə. Data Domain-dən asılıdır (repozitori interfeyslərini icra edir). Presentation Domain-dən asılıdır (Use Cases çağırır, nəticəyə abunə olur).
| Təbəqə | Məzmun | Asılılıqlar |
|---|---|---|
| Domain | Entities, Use Cases, Repository Interfaces | Yox (təmiz Kotlin/Swift) |
| Data | RepositoryImpl, DataSources (API, DB, Cache) | Domain, Retrofit, Room, Ktor |
| Presentation | ViewModels, Views, Composables | Domain, Jetpack, SwiftUI |
Dependency Rule — Clean Architecture-in yeganə sərt qaydası. Mənbə kodu yalnız öz daxilindəki təbəqəyə və ya aşağıdakı təbəqəyə (mərkəzə yaxın) istinad edə bilər. Presentation Domain-i import edir. Domain Data və ya Presentation-ı import etmir. Buna asılılıqların inversiyası (Dependency Inversion Principle) ilə nail olunur: Domain Repository interfeysini təyin edir, Data onu icra edir. Presentation konkret repozitoridən deyil, UseCase abstraksiyasından asılıdır.
Domain — tətbiqin ən sabit təbəqəsi. Entities — freymvorklardan asılı olmayan biznes obyektləri: User, Product, Order. Use Cases — bir invoke metodu (və ya Kotlin-də operator fun invoke) olan, bir ssenarini icra edən siniflər: GetUserUseCase, PlaceOrderUseCase, CalculateTotalUseCase. Repository Interfaces — Domain-də təyin edilən, Data-da icra edilən məlumat giriş abstraksiyaları. Domain Android SDK, iOS UIKit, Retrofit, Room ehtiva etmir — yalnız təmiz Kotlin və ya Swift.
// Entity — biznes obyekti (Domain)
struct User: Equatable {
let id: Int
let name: String
let email: String
}
// Repository Interface — məlumat abstraksiyası (Domain)
protocol UserRepository {
func getUser(id: Int) async throws -> User
func getUsers() async throws -> [User]
}
// Use Case — bir ssenari (Domain)
final class GetUserUseCase {
private let repository: UserRepository
init(repository: UserRepository) {
self.repository = repository
}
func execute(id: Int) async throws -> User {
return try await repository.getUser(id: id)
}
}
Use Case — «bir metodlu sinif» — dogma deyil, praktiki tövsiyədir. Use Case mürəkkəbləşdikdə (validasiya + loqlama + repozitori çağırışı), onun metodları mənaya görə qruplaşdırılır: UserUseCase.getUser, UserUseCase.searchUsers, UserUseCase.deleteUser. Əsas odur ki, Use Case məlumatların haradan gəldiyini (şəbəkə, VB, keş) və kimin göstərdiyini (Compose, SwiftUI) bilməməlidir. IT Sectr-də biz hər bir biznes qaydası, yoxlama və ya iki mənbədən məlumat birləşdirmə əməliyyatı üçün Use Case ayırırıq.
Domain-in təmizliyi təbəqələrin sərhəddində DTO xəritələşdirilməsi ilə əldə edilir. Data təbəqəsi JSON modellərini (DTO) alır, onları Domain Entity-yə xəritələşdirir. Presentation Domain Entity-ni alır, ViewModel-ə (DisplayItem) xəritələşdirir. Domain Entity heç vaxt Retrofit, Room, Codable annotasiyalarını ehtiva etmir — bu, VB Room-dan Realm-ə və ya Retrofit-in Ktor-la əvəz edilməsi zamanı təbəqənin dəyişdirilməyəcəyinə zəmanət verir.
Data Layer — Domain-də təyin edilmiş interfeyslərin icrası. RepositoryImpl (UserRepository-i icra edən siniflər) və DataSources (RemoteDataSource — API, LocalDataSource — VB, CacheDataSource — SharedPreferences/NSUserDefaults) ehtiva edir. Data təbəqəsi Domain-dən (repozitori interfeyslərini və Entities-i import edir) və freymvorklardan (Retrofit, Room, Ktor, CoreData) asılıdır. RepositoryImpl Domain-dən məlumat mənbəyini gizlədir — Use Case məlumatların şəbəkədən və ya keşdən gəldiyini bilmir.
// DTO — şəbəkə üçün model (Data)
data class UserDto(
@SerializedName("id") val id: Int,
@SerializedName("first_name") val firstName: String,
@SerializedName("last_name") val lastName: String,
@SerializedName("email") val email: String
)
// RepositoryImpl — icra (Data)
class UserRepositoryImpl(
private val remoteDataSource: UserRemoteDataSource,
private val localDataSource: UserLocalDataSource
) : UserRepository {
override suspend fun getUser(id: Int): User {
// Keşdən almağa çalışırıq
localDataSource.getUser(id)?.let { return it.toDomain() }
// Yoxdursa — şəbəkədən yükləyirik
val dto = remoteDataSource.fetchUser(id)
val user = dto.toDomain()
localDataSource.saveUser(user)
return user
}
override suspend fun getUsers(): List<User> {
return remoteDataSource.fetchAllUsers().map { it.toDomain() }
}
}
// Mapper — DTO ↔ Domain çevrilməsi
fun UserDto.toDomain() = User(
id = id,
name = "$firstName $lastName",
email = email
)
Keşləmə strategiyası Data Layer-də: RepositoryImpl əvvəlcə yerli yaddaşı yoxlayır, məlumat olmadıqda — şəbəkədən yükləyir və yerli olaraq saxlayır. Şəbəkə əlçatmazdırsa — isStale işarəsi ilə köhnəlmiş məlumatları qaytarır. Domain-də Use Case strategiya haqqında bilmir — User-i Repository.getUser(id) vasitəsilə alır. Strategiyanın dəyişdirilməsi (məsələn, hər 15 dəqiqədən bir keşin etibarsızlaşdırılması) Domain və Presentation-a təsir etmir.
Android-də modulluq — Kotlin Multiplatform Domain-i Android SDK-dan asılılıqları olmayan ayrıca KMP moduluna çıxarmağa imkan verir. Data — Domain-dən asılılığı olan ayrıca modul. Presentation — Domain-dən asılılığı olan Android modulu. Gradle asılılıqları: domain (pure Kotlin), data (domain + Retrofit + Room), app (domain + presentation + Hilt). Bu cür modulluq böyük layihələr üçün məcburidir — CI Domain-i ayrıca qurur, Domain-in unit testləri Android emulyatoru tələb etmir.
Presentation Layer — Clean Architecture-in ən xarici təbəqəsi. ViewModels (Android) / ObservableObject (iOS) və Views (Compose/SwiftUI) ehtiva edir. ViewModel Use Case-i çağırır, nəticəni alır və UI vəziyyətinə (State) çevirir. View State-ə abunə olur və göstərir. Presentation Domain-dən asılıdır — Use Cases və Entities import edir. Presentation Data Layer-i import etmir — məlumatlar Use Case vasitəsilə gəlir, o da daxildə Repository istifadə edir.
Clean Architecture-də ViewModel biznes məntiqi ehtiva etmir — Use Case çağırır. Use Case User qaytarırsa, ViewModel onu UserDisplayItem-ə (name, emailFormatted, avatarUrl) — sırf prezentasiya modelinə çevirir. Use Case DisplayItem haqqında bilmir — Entity qaytarır. Bu bölgü Use Case-in UI olmadan və ViewModel-in UseCase olmadan (mock vasitəsilə) test edilməsinə imkan verir. IT Sectr-də biz ciddi şəkildə riayət edirik: Use Case — biznes məntiqi, ViewModel — yalnız prezentasiya, View — yalnız göstərmə.
// Use Case (Domain) — təmiz biznes məntiqi
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: Int): User {
return repository.getUser(id)
}
}
// ViewModel (Presentation) — yalnız prezentasiya
class UserViewModel(
private val getUserUseCase: GetUserUseCase
) : ViewModel() {
private val _state = MutableStateFlow<UserScreenState>(UserScreenState.Loading)
val state: StateFlow<UserScreenState> = _state.asStateFlow()
fun loadUser(id: Int) {
viewModelScope.launch {
_state.value = UserScreenState.Loading
val user = getUserUseCase(id)
val displayItem = UserDisplayItem(
name = user.name,
email = user.email,
initials = user.name.split(" ").joinToString("") { it.first().toString() }
)
_state.value = UserScreenState.Success(displayItem)
}
}
}
data class UserDisplayItem(
val name: String,
val email: String,
val initials: String
)
sealed interface UserScreenState {
data object Loading : UserScreenState
data class Success(val displayItem: UserDisplayItem) : UserScreenState
data class Error(val message: String) : UserScreenState
}
Navigation Presentation təbəqəsində — xarici halqanın hissəsidir. Clean Architecture naviqasiya mexanizmini təyin etmir — bu, NavController (Compose), NavigationStack (SwiftUI), Coordinator (UIKit) və ya Router (VIPER) ola bilər. Vacibdir: naviqasiya qərarını Presentation verir, lakin naviqasiya Use Case-ə nüfuz etməməlidir. Use Case nəticə qaytarır, ViewModel hansı ekrana keçəcəyinə qərar verir. Clean Architecture-də naviqasiya Domain dəyişdirilmədən əvəz edilə bilən bir detaldır.
Android-də Clean Architecture Gradle modulları vasitəsilə həyata keçirilir: domain (pure Kotlin), data (domain + Retrofit + Room), presentation (domain + Compose). Qovluq strukturu: domain/user/User.kt, GetUserUseCase.kt, UserRepository.kt; data/remote/UserRemoteDataSource.kt, local/UserDao.kt, repository/UserRepositoryImpl.kt; presentation/ui/user/UserViewModel.kt, UserScreen.kt. DI (Hilt) təbəqələri birləşdirir: UserRepositoryImpl domain modulunda UserRepository interfeysinə bağlanır.
iOS-də Clean Architecture ayrıca modullar olmadan SPM və ya Xcode qruplarından istifadə edir (Xcode məhdudiyyətlərinə görə). Domain — UIKit və ya SwiftUI import etməyən faylların olduğu qovluq. Data — APIClient, CoreDataStack, RepositoryImpl olan qovluq. Presentation — ViewModels və SwiftUI Views olan qovluq. DI konstruktor və ya App-də assembly vasitəsilə. Əsas çağırış — UI yeniləmələri üçün MainActor yoxlaması ilə UseCase.execute() vasitəsilə async/await.
// Data Layer: Remote DataSource (iOS)
final class UserRemoteDataSource {
private let apiClient: APIClient
func fetchUser(id: Int) async throws -> UserDTO {
return try await apiClient.get("/users/\(id)")
}
}
// Repository Implementation (Data)
final class UserRepositoryImpl: UserRepository {
private let remote: UserRemoteDataSource
private let local: UserLocalDataSource
func getUser(id: Int) async throws -> User {
if let cached = try await local.getUser(id) {
return cached
}
let dto = try await remote.fetchUser(id)
let user = dto.toDomain()
try await local.saveUser(user)
return user
}
}
// Presentation: ViewModel + SwiftUI View
@MainActor
final class UserViewModel: ObservableObject {
@Published private(set) var state: UserScreenState = .loading
private let getUserUseCase: GetUserUseCase
func loadUser(id: Int) {
Task {
state = .loading
if let user = try? await getUserUseCase.execute(id: id) {
state = .success(user)
} else {
state = .error("Failed to load")
}
}
}
}
IT Sectr layihələrində Clean Architecture — 30 gündən layihələr üçün standartımız. 2022-ci ildən Android/iOS üçün Kotlin Multiplatform ilə üçtəbəqəli arxitekturadan istifadə edirik. Domain — ümumi KMP modulu, Data — platforma modulları (Android-də Retrofit, iOS-də URLSession), Presentation — yerli UI. Bu, iOS və Android arasında biznes məntiqinin 60–80% ümumi kodunu verir, iki ayrıca icra ilə müqayisədə inkişaf vaxtını 30–40% qısaldır.
Tez-tez verilən suallar
Minimal üç: Domain, Data, Presentation. Böyük layihələr üçün Framework (Android SDK/iOS UIKit asılılıqları) və Device (GPS, kamera, sensorlar) əlavə edirlər. Təbəqələrin sayı — sərt qayda deyil, rahatlıq məsələsidir. Əsas olan Dependency Rule-a riayət etməkdir: asılılıqlar içəriyə, Domain-ə yönəldilir. Üçdən başlayıb layihə böyüdükcə təbəqələr əlavə edə bilərsiniz.
Bəli — MVVM ilə müqayisədə 30–50% repozitori interfeysləri, Use Cases və mapper-lərin ayrılması hesabına. Sadə CRUD tətbiqi üçün bu həddindən artıqdır. Clean Architecture test edilə bilmə və təbəqələrin izolyasiyasının inkişaf sürətindən daha vacib olduğu mürəkkəb biznes məntiqi olan layihələr üçün əsaslandırılır. MVP və ya prototip üçün MVVM istifadə edin — Clean Architecture işə salmağı ləngidəcək.
Bəli, bu geniş yayılmış təcrübədir. Use Cases Domain-də qalır, Presentation isə MVI dövründən (Intent → Reducer → State) istifadə edir. Data Layer — eyni, Domain — eyni. Presentation-da MVI proqnozlaşdırıla bilən ekran vəziyyəti verir, Clean Architecture — biznes məntiqinin izolyasiyasını. Bu kombinasiya onlarla tərtibatçısı olan böyük layihələrdə istifadə olunur.
Use Case əməliyyat biznes qaydası ehtiva etdikdə lazımdır: validasiya, iki mənbədən məlumat birləşdirmə, hesablama, loqlama, giriş hüquqlarının yoxlanması. Əlavə məntiq olmadan sadə getUser(id) sorğusu Repository-ni birbaşa ViewModel-dən çağıra bilər. Lakin arxitekturanın vahidliyi üçün bir çox komandalar Repository-nin hər ictimai metodu üçün Use Case yaradır — bu, 5–10% kod əlavə edir, lakin oxumanı asanlaşdırır.
Domain: mock Repository ilə Use Cases-in unit testləri — Android SDK olmadan təmiz Kotlin/Swift. Data: mock/fake DataSource ilə RepositoryImpl-in inteqrasiya testləri. Presentation: mock UseCase ilə ViewModel testləri. Dependency Rule sayəsində hər təbəqə təcrid olunmuş şəkildə test edilir. IT Sectr-də Domain əhatəsi 95%-ə, Data — 70–80%, Presentation — 60–70% çatır.
Nəticələr
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun