Clean Architecture — रॉबर्ट मार्टिन (अंकल बॉब) द्वारा 2012 में प्रस्तावित एक बहु-स्तरीय आर्किटेक्चर, जो एप्लिकेशन को स्वतंत्र परतों में विभाजित करता है: Domain (Entities, Use Cases), Data (Repositories, DataSources) और Presentation (ViewModels, Views)। मुख्य सिद्धांत निर्भरता नियम (Dependency Rule) है: निर्भरताएँ अंदर की ओर इंगित करती हैं, बाहरी परतें आंतरिक परतों पर निर्भर करती हैं, लेकिन इसके विपरीत नहीं। Clean Architecture का उपयोग मोबाइल डेवलपमेंट में उच्च व्यावसायिक तर्क जटिलता वाली परियोजनाओं के लिए किया जाता है। अधिक जानकारी के लिए पुस्तक The Clean Architecture देखें।
मुख्य बिंदु
Clean Architecture — रॉबर्ट मार्टिन (अंकल बॉब) द्वारा 2012 में प्रतिपादित एक आर्किटेक्चरल पैटर्न। मूल विचार सख्त निर्भरता नियम के साथ एप्लिकेशन को परतों में विभाजित करना है: परत के अंदर का कोड बाहरी कोड के बारे में नहीं जानता। बाहरी परतें (UI, फ्रेमवर्क, DB) कार्यान्वयन विवरण हैं। आंतरिक परतें (व्यावसायिक तर्क, उद्यम नियम) एप्लिकेशन का सार हैं।
Clean Architecture की परतें मोबाइल डेवलपमेंट में: 1) Domain — Entities (व्यावसायिक वस्तुएँ) और Use Cases (उपयोग परिदृश्य); 2) Data — RepositoryImpl (रिपॉजिटरी कार्यान्वयन), DataSources (नेटवर्क, DB, कैश); 3) Presentation — ViewModels, Views (Compose/SwiftUI)। Domain सबसे आंतरिक परत है जिसकी कोई निर्भरता नहीं है। Data Domain पर निर्भर करता है (रिपॉजिटरी इंटरफ़ेस लागू करता है)। Presentation Domain पर निर्भर करता है (Use Cases को कॉल करता है, परिणामों की सदस्यता लेता है)।
| परत | इसमें शामिल है | निर्भरताएँ |
|---|---|---|
| Domain | Entities, Use Cases, Repository Interfaces | कोई नहीं (शुद्ध Kotlin/Swift) |
| Data | RepositoryImpl, DataSources (API, DB, कैश) | Domain, Retrofit, Room, Ktor |
| Presentation | ViewModels, Views, Composables | Domain, Jetpack, SwiftUI |
निर्भरता नियम — Clean Architecture का एकमात्र सख्त नियम। स्रोत कोड केवल अपने अंदर की परत या नीचे की परत (केंद्र के करीब) को संदर्भित कर सकता है। Presentation Domain को आयात करता है। Domain Data या Presentation को आयात नहीं करता। यह निर्भरता उलटा सिद्धांत (Dependency Inversion Principle) के माध्यम से प्राप्त होता है: Domain Repository इंटरफ़ेस को परिभाषित करता है, Data इसे लागू करता है। Presentation UseCase एब्स्ट्रैक्शन पर निर्भर करता है, किसी विशिष्ट रिपॉजिटरी पर नहीं।
Domain — एप्लिकेशन की सबसे स्थिर परत। Entities फ्रेमवर्क से स्वतंत्र व्यावसायिक वस्तुएँ हैं: User, Product, Order। Use Cases एकल invoke विधि (या Kotlin में operator fun invoke) वाले वर्ग हैं, जो एक परिदृश्य लागू करते हैं: GetUserUseCase, PlaceOrderUseCase, CalculateTotalUseCase। Repository Interfaces Domain में परिभाषित डेटा एक्सेस एब्स्ट्रैक्शन हैं, जो Data में लागू होते हैं। Domain में Android SDK, iOS UIKit, Retrofit, Room नहीं है — केवल शुद्ध Kotlin या Swift।
// Entity — व्यावसायिक वस्तु (Domain)
struct User: Equatable {
let id: Int
let name: String
let email: String
}
// Repository Interface — डेटा एब्स्ट्रैक्शन (Domain)
protocol UserRepository {
func getUser(id: Int) async throws -> User
func getUsers() async throws -> [User]
}
// Use Case — एक परिदृश्य (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 — «एक विधि वाला वर्ग» कोई हठधर्मिता नहीं बल्कि एक व्यावहारिक सिफारिश है। जब Use Case अधिक जटिल हो जाता है (सत्यापन + लॉगिंग + रिपॉजिटरी कॉल), तो इसकी विधियाँ अर्थ के अनुसार समूहित की जाती हैं: UserUseCase.getUser, UserUseCase.searchUsers, UserUseCase.deleteUser। मुख्य बात यह है कि Use Case को यह नहीं जानना चाहिए कि डेटा कहाँ से आता है (नेटवर्क, DB, कैश) या इसे कौन प्रदर्शित करता है (Compose, SwiftUI)। IT Sectr में हम प्रत्येक ऑपरेशन के लिए एक Use Case आवंटित करते हैं जिसमें व्यावसायिक नियम, सत्यापन या दो स्रोतों से डेटा संयोजन होता है।
Domain की शुद्धता परत सीमाओं पर DTO मैपिंग के माध्यम से प्राप्त की जाती है। Data परत JSON मॉडल (DTO) प्राप्त करती है, उन्हें Domain Entity में मैप करती है। Presentation Domain Entity प्राप्त करता है, उन्हें ViewModel (DisplayItem) में मैप करता है। Domain Entity में कभी भी Retrofit, Room, या Codable एनोटेशन नहीं होते — यह गारंटी देता है कि DB को Room से Realm में बदलने या Retrofit को Ktor से बदलने पर परत को बदलने की आवश्यकता नहीं होगी।
Data Layer — Domain में परिभाषित इंटरफ़ेस का कार्यान्वयन। इसमें RepositoryImpl (UserRepository को लागू करने वाले वर्ग) और DataSources (RemoteDataSource — API, LocalDataSource — DB, CacheDataSource — SharedPreferences/NSUserDefaults) शामिल हैं। Data परत Domain (रिपॉजिटरी इंटरफ़ेस और Entities आयात करती है) और फ्रेमवर्क (Retrofit, Room, Ktor, CoreData) पर निर्भर करती है। RepositoryImpl Domain से डेटा स्रोत छिपाता है — Use Case को नहीं पता कि डेटा नेटवर्क से आया या कैश से।
// DTO — नेटवर्क के लिए मॉडल (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 — कार्यान्वयन (Data)
class UserRepositoryImpl(
private val remoteDataSource: UserRemoteDataSource,
private val localDataSource: UserLocalDataSource
) : UserRepository {
override suspend fun getUser(id: Int): User {
// कैश से प्राप्त करने का प्रयास
localDataSource.getUser(id)?.let { return it.toDomain() }
// यदि नहीं — नेटवर्क से लोड करें
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 रूपांतरण
fun UserDto.toDomain() = User(
id = id,
name = "$firstName $lastName",
email = email
)
कैशिंग रणनीति Data Layer में: RepositoryImpl पहले स्थानीय स्टोरेज की जाँच करता है; यदि डेटा नहीं मिलता है, तो नेटवर्क से लोड करता है और स्थानीय रूप से सहेजता है। यदि नेटवर्क उपलब्ध नहीं है, तो isStale फ़्लैग के साथ पुराना डेटा लौटाता है। Domain में Use Case रणनीति के बारे में नहीं जानता — यह Repository.getUser(id) के माध्यम से User प्राप्त करता है। रणनीति बदलना (जैसे, हर 15 मिनट में कैश अमान्यकरण) Domain और Presentation को प्रभावित नहीं करता।
Android में मॉड्यूलरिटी — Kotlin Multiplatform Android SDK निर्भरताओं के बिना Domain को एक अलग KMP मॉड्यूल में निकालने की अनुमति देता है। Data Domain पर निर्भरता वाला एक अलग मॉड्यूल है। Presentation Domain पर निर्भरता वाला एक Android मॉड्यूल है। Gradle निर्भरताएँ: domain (शुद्ध Kotlin), data (domain + Retrofit + Room), app (domain + presentation + Hilt)। बड़ी परियोजनाओं के लिए ऐसी मॉड्यूलरिटी आवश्यक है — CI Domain को अलग से बनाता है, Domain यूनिट टेस्ट के लिए Android एमुलेटर की आवश्यकता नहीं होती।
Presentation Layer — Clean Architecture की सबसे बाहरी परत। इसमें ViewModels (Android) / ObservableObject (iOS) और Views (Compose/SwiftUI) शामिल हैं। ViewModel Use Case को कॉल करता है, परिणाम प्राप्त करता है और UI स्थिति में बदल देता है। View स्थिति की सदस्यता लेता है और प्रदर्शित करता है। Presentation Domain पर निर्भर करता है — Use Cases और Entities आयात करता है। Presentation Data Layer आयात नहीं करता — डेटा Use Case के माध्यम से आता है, जो आंतरिक रूप से Repository का उपयोग करता है।
Clean Architecture में ViewModel में कोई व्यावसायिक तर्क नहीं है — यह Use Case को कॉल करता है। यदि Use Case User लौटाता है, तो ViewModel इसे UserDisplayItem (name, emailFormatted, avatarUrl) में बदल देता है — एक विशुद्ध प्रस्तुति मॉडल। Use Case DisplayItem के बारे में नहीं जानता — यह Entity लौटाता है। यह पृथक्करण UI के बिना Use Case और UseCase के बिना ViewModel (मॉक के माध्यम से) का परीक्षण करने की अनुमति देता है। IT Sectr में हम सख्ती से पालन करते हैं: Use Case — व्यावसायिक तर्क, ViewModel — केवल प्रस्तुति, View — केवल प्रदर्शन।
// Use Case (Domain) — शुद्ध व्यावसायिक तर्क
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: Int): User {
return repository.getUser(id)
}
}
// ViewModel (Presentation) — केवल प्रस्तुति
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
}
नेविगेशन Presentation परत में भी बाहरी रिंग का हिस्सा है। Clean Architecture कोई नेविगेशन तंत्र निर्धारित नहीं करता — यह NavController (Compose), NavigationStack (SwiftUI), Coordinator (UIKit) या Router (VIPER) हो सकता है। महत्वपूर्ण बात यह है कि नेविगेशन निर्णय Presentation लेता है, लेकिन नेविगेशन को Use Case में प्रवेश नहीं करना चाहिए। Use Case परिणाम लौटाता है, ViewModel तय करता है कि किस स्क्रीन पर जाना है। Clean Architecture में, नेविगेशन एक विवरण है जिसे Domain बदले बिना बदला जा सकता है।
Android पर Clean Architecture Gradle मॉड्यूल के माध्यम से कार्यान्वित किया जाता है: domain (शुद्ध Kotlin), data (domain + Retrofit + Room), presentation (domain + Compose)। फ़ोल्डर संरचना: 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) परतों को जोड़ता है: UserRepositoryImpl domain मॉड्यूल में UserRepository इंटरफ़ेस से बंधा होता है।
iOS पर Clean Architecture अलग मॉड्यूल के बिना SPM या Xcode समूहों का उपयोग करता है (Xcode सीमाओं के कारण)। Domain — एक फ़ोल्डर जिसमें फ़ाइलें UIKit या SwiftUI आयात नहीं करतीं। Data — APIClient, CoreDataStack, RepositoryImpl वाला फ़ोल्डर। Presentation — ViewModels और SwiftUI Views वाला फ़ोल्डर। कंस्ट्रक्टर या ऐप में असेंबली के माध्यम से DI। मुख्य कॉल UI अपडेट के लिए MainActor जाँच के साथ UseCase.execute() के माध्यम से 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 परियोजनाओं में Clean Architecture — 30 दिनों से परियोजनाओं के लिए हमारा मानक। हम 2022 से Android/iOS के लिए Kotlin Multiplatform के साथ तीन-परत वास्तुकला का उपयोग कर रहे हैं। Domain — एक साझा KMP मॉड्यूल, Data — प्लेटफ़ॉर्म मॉड्यूल (Android पर Retrofit, iOS पर URLSession), Presentation — मूल UI। यह iOS और Android के बीच 60–80% साझा व्यावसायिक तर्क कोड देता है, जो दो अलग-अलग कार्यान्वयनों की तुलना में विकास समय को 30–40% कम करता है।
अक्सर पूछे जाने वाले प्रश्न
न्यूनतम तीन: Domain, Data, Presentation। बड़ी परियोजनाओं के लिए, Framework (Android SDK/iOS UIKit निर्भरताएँ) और Device (GPS, कैमरा, सेंसर) जोड़े जाते हैं। परतों की संख्या कोई सख्त नियम नहीं बल्कि सुविधा का मामला है। मुख्य बात निर्भरता नियम का पालन करना है: निर्भरताएँ अंदर की ओर, Domain की ओर इंगित करती हैं। आप तीन से शुरू कर सकते हैं और परियोजना के बढ़ने पर परतें जोड़ सकते हैं।
हाँ — MVVM की तुलना में 30–50% तक रिपॉजिटरी इंटरफ़ेस, Use Cases और मैपर्स के निष्कर्षण के कारण। एक साधारण CRUD एप्लिकेशन के लिए यह अत्यधिक है। Clean Architecture जटिल व्यावसायिक तर्क वाली परियोजनाओं के लिए उचित है जहाँ परीक्षण क्षमता और परत अलगाव विकास गति से अधिक महत्वपूर्ण हैं। MVP या प्रोटोटाइप के लिए MVVM का उपयोग करें — Clean Architecture शुरुआत को धीमा कर देगी।
हाँ, यह सामान्य प्रथा है। Use Cases Domain में रहते हैं, जबकि Presentation MVI चक्र (Intent → Reducer → State) का उपयोग करता है। Data Layer वही रहता है, Domain वही रहता है। Presentation में MVI पूर्वानुमानित स्क्रीन स्थिति देता है, Clean Architecture व्यावसायिक तर्क अलगाव देता है। यह संयोजन दर्जनों डेवलपर्स वाली बड़ी परियोजनाओं में उपयोग किया जाता है।
Use Case की आवश्यकता तब होती है जब ऑपरेशन में व्यावसायिक नियम शामिल हो: सत्यापन, दो स्रोतों से डेटा का संयोजन, गणना, लॉगिंग, पहुँच अनुमति जाँच। बिना अतिरिक्त तर्क के एक सरल getUser(id) अनुरोध सीधे ViewModel से Repository को कॉल कर सकता है। हालाँकि, आर्किटेक्चरल स्थिरता के लिए, कई टीमें Repository की प्रत्येक सार्वजनिक विधि के लिए Use Case बनाती हैं — इससे 5–10% कोड जुड़ता है लेकिन पढ़ना सरल हो जाता है।
Domain: मॉक Repository के साथ Use Cases के यूनिट टेस्ट — Android SDK के बिना शुद्ध Kotlin/Swift। Data: मॉक/फ़ेक DataSource के साथ RepositoryImpl के एकीकरण टेस्ट। Presentation: मॉक UseCase के साथ ViewModel टेस्ट। निर्भरता नियम के कारण, प्रत्येक परत का अलग-अलग परीक्षण किया जाता है। IT Sectr में, Domain कवरेज 95%, Data — 70–80%, Presentation — 60–70% तक पहुँचता है।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें