Clean Architecture — مبانی، لایه‌های Entities، Use Cases و Gateways

نویسنده: IT Sectr منتشر شده: 2026-02-17 زمان مطالعه: 10 دقیقه

Clean Architecture — معماری چندلایه‌ای که توسط رابرت مارتین (Uncle Bob) در سال ۲۰۱۲ ارائه شد، برنامه را به لایه‌های مستقل تقسیم می‌کند: Domain (Entities، Use Cases)، Data (Repositories، DataSources) و Presentation (ViewModels، Views). اصل اصلی — Dependency Rule: وابستگی‌ها به سمت داخل هدایت می‌شوند، لایه‌های بیرونی به لایه‌های داخلی وابسته هستند، نه برعکس. Clean Architecture در توسعه موبایل برای پروژه‌هایی با پیچیدگی بالای منطق تجاری استفاده می‌شود. بیشتر — در کتاب The Clean Architecture.

نکات اصلی

  • Clean Architecture — سه لایه: Domain (منطق تجاری)، Data (داده‌ها)، Presentation (UI) با Dependency Rule
  • Dependency Rule — وابستگی‌ها به سمت داخل هستند، Domain از Data و Presentation اطلاعی ندارد
  • Use Cases (Interactors) — سناریوهای منطق تجاری، هر Use Case — یک کلاس با یک متد
  • Repository Interface — انتزاع داده در Domain، پیاده‌سازی در لایه Data
  • قابلیت تست — Domain و Use Cases با تست‌های واحد بدون Android SDK و iOS UIKit آزمایش می‌شوند

Clean Architecture — مبانی معماری چندلایه

Clean Architecture — الگوی معماری فرموله‌شده توسط رابرت مارتین (Uncle Bob) در سال ۲۰۱۲. ایده اصلی — تقسیم برنامه به لایه‌ها با قانون سخت وابستگی: کد داخل یک لایه از کد بیرون اطلاعی ندارد. لایه‌های بیرونی (UI، فریم‌ورک‌ها، پایگاه داده) — جزئیات پیاده‌سازی. لایه‌های داخلی (منطق تجاری، قوانین سازمانی) — جوهر برنامه.

لایه‌های Clean Architecture در توسعه موبایل: ۱) Domain — Entities (اشیاء تجاری) و Use Cases (موارد استفاده)؛ ۲) Data — RepositoryImpl (پیاده‌سازی مخازن)، DataSources (شبکه، پایگاه داده، کش)؛ ۳) Presentation — ViewModels، Views (Compose/SwiftUI). Domain — درونی‌ترین لایه بدون وابستگی. Data به Domain وابسته است (اینترفیس‌های مخزن را پیاده‌سازی می‌کند). Presentation به Domain وابسته است (Use Cases را فراخوانی می‌کند، در نتیجه مشترک می‌شود).

لایهمحتوياتوابستگی‌ها
DomainEntities، Use Cases، Repository Interfacesندارد (Kotlin/Swift خالص)
DataRepositoryImpl، DataSources (API، DB، Cache)Domain، Retrofit، Room، Ktor
PresentationViewModels، Views، ComposablesDomain، Jetpack، SwiftUI

Dependency Rule — تنها قانون سخت Clean Architecture. کد منبع فقط می‌تواند به لایه داخل خود یا لایه پایین‌تر (نزدیک‌تر به مرکز) ارجاع دهد. Presentation Domain را import می‌کند. Domain Data یا Presentation را import نمی‌کند. این از طریق اصل معکوس‌سازی وابستگی (Dependency Inversion Principle) به دست می‌آید: Domain اینترفیس Repository را تعریف می‌کند، Data آن را پیاده‌سازی می‌کند. Presentation به انتزاع UseCase وابسته است، نه به مخزن مشخص.

Domain Layer: Entities، Use Cases و Repository Interfaces

Domain — پایدارترین لایه برنامه. Entities — اشیاء تجاری مستقل از فریم‌ورک‌ها: User، Product، Order. Use Cases — کلاس‌هایی با یک متد invoke (یا operator fun invoke در Kotlin) که یک سناریو را پیاده‌سازی می‌کنند: GetUserUseCase، PlaceOrderUseCase، CalculateTotalUseCase. Repository Interfaces — انتزاعات دسترسی به داده که در Domain تعریف و در Data پیاده‌سازی می‌شوند. Domain شامل Android SDK، iOS UIKit، Retrofit، Room نیست — فقط Kotlin یا Swift خالص.

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 نباید بداند داده‌ها از کجا می‌آیند (شبکه، پایگاه داده، کش) و چه کسی آنها را نمایش می‌دهد (Compose، SwiftUI). در IT Sectr ما برای هر عملیاتی که قانون تجاری، بررسی یا ترکیب داده از دو منبع دارد، Use Case جداگانه‌ای اختصاص می‌دهیم.

خلوص Domain از طریق نگاشت DTO در مرز لایه‌ها به دست می‌آید. لایه Data مدل‌های JSON (DTO) را دریافت می‌کند، آنها را به Entity Domain نگاشت می‌کند. Presentation Entity Domain را دریافت می‌کند، به ViewModel (DisplayItem) نگاشت می‌کند. Entity Domain هرگز حاوی حاشیه‌نویسی‌های Retrofit، Room، Codable نیست — این تضمین می‌کند که لایه هنگام تغییر پایگاه داده از Room به Realm یا جایگزینی Retrofit با Ktor نیازی به تغییر نداشته باشد.

Data Layer: Repository Implementation و DataSources

Data Layer — پیاده‌سازی اینترفیس‌های تعریف‌شده در Domain. شامل RepositoryImpl (کلاس‌هایی که UserRepository را پیاده‌سازی می‌کنند) و DataSources (RemoteDataSource — API، LocalDataSource — پایگاه داده، CacheDataSource — SharedPreferences/NSUserDefaults). لایه Data به Domain (اینترفیس‌های مخزن و Entities را import می‌کند) و به فریم‌ورک‌ها (Retrofit، Room، Ktor، CoreData) وابسته است. RepositoryImpl منبع داده را از Domain پنهان می‌کند — Use Case نمی‌داند داده‌ها از شبکه آمده یا کش.

kotlin
// 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 برمی‌گرداند. Use Case در Domain از استراتژی اطلاعی ندارد — User را از طریق Repository.getUser(id) دریافت می‌کند. تغییر استراتژی (مثلاً ابطال کش هر ۱۵ دقیقه) بر Domain و Presentation تأثیر نمی‌گذارد.

ماژولار بودن در Android — Kotlin Multiplatform اجازه می‌دهد Domain به یک ماژول KMP جداگانه بدون وابستگی به Android SDK منتقل شود. Data — ماژول جداگانه با وابستگی به Domain. Presentation — ماژول Android با وابستگی به Domain. وابستگی‌های Gradle: domain (pure Kotlin)، data (domain + Retrofit + Room)، app (domain + presentation + Hilt). چنین ماژولاری برای پروژه‌های بزرگ الزامی است — CI Domain را جداگانه می‌سازد، تست‌های واحد Domain به شبیه‌ساز Android نیاز ندارند.

Presentation Layer: ViewModels و Views

Presentation Layer — بیرونی‌ترین لایه Clean Architecture. شامل ViewModels (Android) / ObservableObject (iOS) و Views (Compose/SwiftUI). ViewModel Use Case را فراخوانی می‌کند، نتیجه را دریافت و به حالت UI (State) تبدیل می‌کند. View در State مشترک شده و نمایش می‌دهد. Presentation به Domain وابسته است — Use Cases و Entities را import می‌کند. Presentation Data Layer را import نمی‌کند — داده‌ها از طریق Use Case می‌آیند که در داخل از Repository استفاده می‌کند.

ViewModel در Clean Architecture شامل منطق تجاری نیست — Use Case را فراخوانی می‌کند. اگر Use Case User را برگرداند، ViewModel آن را به UserDisplayItem (name، emailFormatted، avatarUrl) — یک مدل صرفاً نمایشی — تبدیل می‌کند. Use Case از DisplayItem اطلاعی ندارد — Entity برمی‌گرداند. این تفکیک اجازه می‌دهد Use Case بدون UI و ViewModel بدون UseCase (از طریق mock) تست شوند. در IT Sectr ما به طور دقیق رعایت می‌کنیم: Use Case — منطق تجاری، ViewModel — فقط نمایش، View — فقط ارائه.

kotlin
// 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
}

Navigation در لایه Presentation — بخشی از حلقه بیرونی است. Clean Architecture مکانیسم ناوبری را تجویز نمی‌کند — می‌تواند NavController (Compose)، NavigationStack (SwiftUI)، Coordinator (UIKit) یا Router (VIPER) باشد. مهم: تصمیم ناوبری توسط Presentation گرفته می‌شود، اما ناوبری نباید به Use Case نفوذ کند. Use Case نتیجه را برمی‌گرداند، ViewModel تصمیم می‌گیرد به کدام صفحه برود. در Clean Architecture ناوبری جزییاتی است که بدون تغییر Domain قابل تعویض است.

Clean Architecture در iOS و Android: نمونه کد

Clean Architecture در Android از طریق ماژول‌های Gradle پیاده‌سازی می‌شود: domain (pure 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 به اینترفیس UserRepository در ماژول domain متصل می‌شود.

Clean Architecture در iOS از SPM یا گروه‌های Xcode بدون ماژول‌های جداگانه (به دلیل محدودیت‌های Xcode) استفاده می‌کند. Domain — پوشه‌ای با فایل‌هایی که UIKit یا SwiftUI را import نمی‌کنند. Data — پوشه‌ای با APIClient، CoreDataStack، RepositoryImpl. Presentation — پوشه‌ای با ViewModels و SwiftUI Views. DI از طریق سازنده یا اسمبلی در App. فراخوانی اصلی — async/await از طریق UseCase.execute() با بررسی MainActor برای به‌روزرسانی‌های UI.

swift
// 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")
            }
        }
    }
}

Clean Architecture در پروژه‌های IT Sectr — استاندارد ما برای پروژه‌های از ۳۰ روز. ما از معماری سه‌لایه با Kotlin Multiplatform برای Android/iOS از سال ۲۰۲۲ استفاده می‌کنیم. Domain — ماژول مشترک KMP، Data — ماژول‌های پلتفرمی (Retrofit در Android، URLSession در iOS)، Presentation — UI بومی. این ۶۰–۸۰٪ کد مشترک منطق تجاری بین iOS و Android می‌دهد و زمان توسعه را ۳۰–۴۰٪ در مقایسه با دو پیاده‌سازی جداگانه کاهش می‌دهد.

سوالات متداول

چند لایه باید در Clean Architecture باشد؟

حداقل سه: Domain، Data، Presentation. برای پروژه‌های بزرگ Framework (وابستگی‌های Android SDK/iOS UIKit) و Device (GPS، دوربین، سنسورها) اضافه می‌کنند. تعداد لایه‌ها — قانون سخت نیست، بحث راحتی است. مهم رعایت Dependency Rule است: وابستگی‌ها به سمت داخل، به Domain. می‌توان با سه لایه شروع کرد و با رشد پروژه لایه‌ها را اضافه کرد.

آیا Clean Architecture مقدار کد را افزایش می‌دهد؟

بله — ۳۰–۵۰٪ در مقایسه با MVVM به دلیل جدا کردن اینترفیس‌های مخازن، Use Cases و مپرها. برای برنامه ساده CRUD این اضافی است. Clean Architecture برای پروژه‌هایی با منطق تجاری پیچیده توجیه‌پذیر است، جایی که قابلیت تست و ایزولاسیون لایه‌ها مهم‌تر از سرعت توسعه است. برای MVP یا نمونه اولیه از MVVM استفاده کنید — Clean Architecture راه‌اندازی را کند می‌کند.

آیا می‌توان Clean Architecture را با MVI ترکیب کرد؟

بله، این یک روش رایج است. Use Cases در Domain باقی می‌مانند و Presentation از چرخه MVI (Intent → Reducer → State) استفاده می‌کند. Data Layer — یکسان، Domain — یکسان. MVI در Presentation حالت قابل پیش‌بینی صفحه را می‌دهد، Clean Architecture — ایزولاسیون منطق تجاری. این ترکیب در پروژه‌های بزرگ با ده‌ها توسعه‌دهنده استفاده می‌شود.

آیا برای هر درخواست داده به Use Cases نیاز است؟

Use Case زمانی نیاز است که عملیات شامل قانون تجاری باشد: اعتبارسنجی، ترکیب داده از دو منبع، محاسبه، لاگ‌گیری، بررسی مجوزهای دسترسی. درخواست ساده getUser(id) بدون منطق اضافی می‌تواند Repository را مستقیماً از ViewModel فراخوانی کند. اما برای یکنواختی معماری، بسیاری از تیم‌ها برای هر متد عمومی Repository Use Case ایجاد می‌کنند — این ۵–۱۰٪ کد اضافه می‌کند اما خواندن را ساده‌تر می‌کند.

چگونه Clean Architecture را تست کنیم؟

Domain: تست‌های واحد Use Cases با mock Repository — Kotlin/Swift خالص بدون Android SDK. Data: تست‌های یکپارچه‌سازی RepositoryImpl با mock/fake DataSource. Presentation: تست‌های ViewModel با mock UseCase. به لطف Dependency Rule هر لایه به صورت ایزوله تست می‌شود. در IT Sectr پوشش Domain به ۹۵٪، Data — ۷۰–۸۰٪، Presentation — ۶۰–۷۰٪ می‌رسد.

خلاصه

  • Clean Architecture — سه لایه (Domain، Data، Presentation) با Dependency Rule به سمت داخل
  • Dependency Rule — Domain از Data و Presentation اطلاعی ندارد، ایزولاسیون از طریق اینترفیس‌ها
  • Domain — Entities، Use Cases، Repository Interfaces — Kotlin/Swift خالص بدون فریم‌ورک
  • Data — RepositoryImpl، DataSources (شبکه، پایگاه داده، کش) — پیاده‌سازی اینترفیس‌های Domain
  • Presentation — ViewModels، Views — فقط نمایش، منطق تجاری در Use Cases
  • تست — Domain با تست‌های واحد ۹۰–۹۵٪ پوشش داده می‌شود
  • KMP — Clean Architecture با Kotlin Multiplatform ۶۰–۸۰٪ کد مشترک iOS + Android می‌دهد

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید