Clean Architecture — الأساسيات، طبقات الكيانات وحالات الاستخدام والبوابات

المؤلف: IT Sectr نُشر: 2026-02-17 وقت القراءة: 10 دق

Clean Architecture — هندسة معمارية متعددة الطبقات اقترحها روبرت مارتن (Uncle Bob) في عام 2012، تقسم التطبيق إلى طبقات مستقلة: Domain (الكيانات، حالات الاستخدام)، Data (المستودعات، مصادر البيانات) وPresentation (عارضات البيانات، الواجهات). المبدأ الرئيسي هو قاعدة التبعية: التبعيات تتجه إلى الداخل، الطبقات الخارجية تعتمد على الداخلية وليس العكس. تُستخدم Clean Architecture في تطوير التطبيقات المحمولة للمشاريع ذات التعقيد العالي في منطق الأعمال. المزيد في كتاب The Clean Architecture.

النقاط الرئيسية

  • Clean Architecture — ثلاث طبقات: Domain (منطق الأعمال)، Data (البيانات)، Presentation (واجهة المستخدم) مع قاعدة التبعية
  • قاعدة التبعية — التبعيات تتجه إلى الداخل، Domain لا يعرف شيئاً عن Data وPresentation
  • حالات الاستخدام (المتفاعلات) — سيناريوهات منطق الأعمال، كل حالة استخدام عبارة عن فئة واحدة بطريقة واحدة
  • واجهة المستودع — تجريد البيانات في Domain، التنفيذ في طبقة Data
  • قابلية الاختبار — يتم اختبار Domain وحالات الاستخدام باختبارات الوحدة بدون Android SDK وiOS UIKit

Clean Architecture — أساسيات الهندسة المعمارية متعددة الطبقات

Clean Architecture — نمط معماري صاغه روبرت مارتن (Uncle Bob) في عام 2012. الفكرة الأساسية هي تقسيم التطبيق إلى طبقات بقاعدة تبعية صارمة: الكود داخل الطبقة لا يعرف شيئاً عن الكود الخارجي. الطبقات الخارجية (واجهة المستخدم، الأطر، قاعدة البيانات) هي تفاصيل تنفيذ. الطبقات الداخلية (منطق الأعمال، قواعد المؤسسة) هي جوهر التطبيق.

طبقات Clean Architecture في تطوير التطبيقات المحمولة: 1) Domain — الكيانات (كائنات الأعمال) وحالات الاستخدام (سيناريوهات الاستخدام)؛ 2) Data — RepositoryImpl (تطبيقات المستودعات)، DataSources (الشبكة، قاعدة البيانات، التخزين المؤقت)؛ 3) Presentation — عارضات البيانات، الواجهات (Compose/SwiftUI). Domain هي الطبقة الأعمق بدون تبعيات. Data تعتمد على Domain (تنفذ واجهات المستودع). Presentation تعتمد على Domain (تستدعي حالات الاستخدام، تشترك في النتائج).

الطبقةتحتويالتبعيات
Domainالكيانات، حالات الاستخدام، واجهات المستودعلا شيء (Kotlin/Swift نقي)
DataRepositoryImpl، DataSources (API، قاعدة البيانات، التخزين المؤقت)Domain، Retrofit، Room، Ktor
Presentationعارضات البيانات، الواجهات، ComposablesDomain، Jetpack، SwiftUI

قاعدة التبعية — القاعدة الصارمة الوحيدة في Clean Architecture. يمكن للكود المصدر أن يشير فقط إلى طبقة داخل نفسه أو إلى طبقة أدنى (أقرب إلى المركز). Presentation تستورد Domain. Domain لا تستورد Data أو Presentation. يتم تحقيق ذلك من خلال مبدأ عكس التبعية: Domain تعرف واجهة Repository، Data تنفذها. Presentation تعتمد على تجريد UseCase وليس على مستودع محدد.

طبقة Domain: الكيانات وحالات الاستخدام وواجهات المستودع

Domain — الطبقة الأكثر استقراراً في التطبيق. الكيانات هي كائنات أعمال مستقلة عن الأطر: User وProduct وOrder. حالات الاستخدام هي فئات بطريقة invoke واحدة (أو operator fun invoke في Kotlin)، تنفذ سيناريو واحد: GetUserUseCase وPlaceOrderUseCase وCalculateTotalUseCase. واجهات المستودع هي تجريدات الوصول إلى البيانات المعرفة في 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)
    }
}

حالة الاستخدام — «فئة بطريقة واحدة» ليست عقيدة بل توصية عملية. عندما تصبح حالة الاستخدام أكثر تعقيداً (تحقق + تسجيل + استدعاء مستودع)، يتم تجميع طرقها حسب المعنى: UserUseCase.getUser وUserUseCase.searchUsers وUserUseCase.deleteUser. المهم أن حالة الاستخدام لا يجب أن تعرف من أين تأتي البيانات (شبكة، قاعدة بيانات، تخزين مؤقت) أو من يعرضها (Compose، SwiftUI). في IT Sectr نخصص حالة استخدام لكل عملية لها قاعدة أعمال أو تحقق أو دمج بيانات من مصدرين.

نقاء Domain يتحقق من خلال تعيين DTO على حدود الطبقات. طبقة Data تستقبل نماذج JSON (DTO)، تعينها إلى كيانات Domain. Presentation تستقبل كيانات Domain، تعينها إلى عارضات البيانات (DisplayItem). كيان Domain لا يحتوي أبداً على تعليقات Retrofit أو Room أو Codable — وهذا يضمن أن الطبقة لن تحتاج إلى التغيير عند تبديل قاعدة البيانات من Room إلى Realm أو عند استبدال Retrofit بـ Ktor.

طبقة Data: تنفيذ المستودع ومصادر البيانات

طبقة Data — تنفيذ الواجهات المعرفة في Domain. تحتوي على RepositoryImpl (الفئات التي تنفذ UserRepository) وDataSources (RemoteDataSource — API، وLocalDataSource — قاعدة البيانات، وCacheDataSource — SharedPreferences/NSUserDefaults). طبقة Data تعتمد على Domain (تستورد واجهات المستودع والكيانات) وعلى الأطر (Retrofit، Room، Ktor، CoreData). RepositoryImpl يخفي مصدر البيانات عن Domain — حالة الاستخدام لا تعرف ما إذا كانت البيانات جاءت من الشبكة أو التخزين المؤقت.

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: RepositoryImpl يتحقق أولاً من التخزين المحلي؛ إذا لم يتم العثور على بيانات، يقوم بتحميلها من الشبكة وحفظها محلياً. إذا كانت الشبكة غير متاحة، يقوم بإرجاع بيانات قديمة مع علامة isStale. حالة الاستخدام في Domain لا تعرف عن الاستراتيجية — تستقبل User عبر Repository.getUser(id). تغيير الاستراتيجية (مثلاً، إبطال التخزين المؤقت كل 15 دقيقة) لا يؤثر على Domain أو Presentation.

النمطية في Android — Kotlin Multiplatform تسمح باستخراج Domain إلى وحدة KMP منفصلة بدون تبعيات Android SDK. Data هي وحدة منفصلة تعتمد على Domain. Presentation هي وحدة Android تعتمد على Domain. تبعيات Gradle: domain (Kotlin نقي)، data (domain + Retrofit + Room)، app (domain + presentation + Hilt). هذه النمطية ضرورية للمشاريع الكبيرة — CI يبني Domain بشكل منفصل، اختبارات الوحدة لـ Domain لا تتطلب محاكي Android.

طبقة Presentation: عارضات البيانات والواجهات

طبقة Presentation — الطبقة الأكثر خارجية في Clean Architecture. تحتوي على عارضات البيانات (Android) / ObservableObject (iOS) والواجهات (Compose/SwiftUI). عارضة البيانات تستدعي حالة استخدام، تستقبل النتيجة وتحولها إلى حالة واجهة المستخدم. الواجهة تشترك في الحالة وتعرضها. Presentation تعتمد على Domain — تستورد حالات الاستخدام والكيانات. Presentation لا تستورد طبقة Data — البيانات تأتي عبر حالة الاستخدام التي تستخدم داخلياً مستودعاً.

عارضة البيانات في Clean Architecture لا تحتوي على منطق أعمال — تستدعي حالة الاستخدام. إذا أعادت حالة الاستخدام User، تقوم عارضة البيانات بتحويله إلى UserDisplayItem (name وemailFormatted وavatarUrl) — نموذج عرض بحت. حالة الاستخدام لا تعرف عن DisplayItem — تعيد كياناً. هذا الفصل يسمح باختبار حالة الاستخدام بدون واجهة المستخدم واختبار عارضة البيانات بدون حالة الاستخدام (عبر النماذج الوهمية). في IT Sectr نتبع بدقة: حالة الاستخدام — منطق الأعمال، عارضة البيانات — عرض فقط، الواجهة — عرض فقط.

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
}

التنقل في طبقة Presentation هو أيضاً جزء من الحلقة الخارجية. Clean Architecture لا تفرض آلية تنقل — يمكن أن يكون NavController (Compose) أو NavigationStack (SwiftUI) أو Coordinator (UIKit) أو Router (VIPER). المهم أن قرارات التنقل يتخذها Presentation، لكن التنقل لا يجب أن يتسرب إلى حالة الاستخدام. حالة الاستخدام تعيد نتيجة، عارضة البيانات تقرر أي شاشة سينتقل إليها. في Clean Architecture، التنقل هو تفصيل يمكن استبداله دون تغيير Domain.

Clean Architecture على iOS وAndroid: أمثلة برمجية

Clean Architecture على Android يتم تنفيذها عبر وحدات 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 يرتبط بواجهة UserRepository في وحدة domain.

Clean Architecture على iOS تستخدم SPM أو مجموعات Xcode بدون وحدات منفصلة (بسبب قيود Xcode). Domain — مجلد بملفات لا تستورد UIKit أو SwiftUI. Data — مجلد مع APIClient وCoreDataStack وRepositoryImpl. Presentation — مجلد مع عارضات البيانات وواجهات SwiftUI. DI عبر المُنشئ أو التجميع في التطبيق. الاستدعاء الرئيسي هو async/await عبر UseCase.execute() مع التحقق من MainActor لتحديثات واجهة المستخدم.

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 — معيارنا للمشاريع من 30 يوماً فصاعداً. نحن نستخدم هندسة ثلاثية الطبقات مع Kotlin Multiplatform لنظامي Android/iOS منذ عام 2022. Domain — وحدة KMP مشتركة، Data — وحدات منصة (Retrofit على Android، URLSession على iOS)، Presentation — واجهة مستخدم أصلية. وهذا يعطي 60–80% من كود منطق الأعمال المشترك بين iOS وAndroid، مما يقلل وقت التطوير بنسبة 30–40% مقارنة بتطبيقين منفصلين.

الأسئلة الشائعة

كم عدد الطبقات التي يجب أن تحتويها Clean Architecture؟

ثلاثة كحد أدنى: Domain وData وPresentation. للمشاريع الكبيرة، تُضاف Framework (تبعيات Android SDK/iOS UIKit) وDevice (GPS، الكاميرا، أجهزة الاستشعار). عدد الطبقات ليس قاعدة صارمة بل مسألة راحة. المهم هو اتباع قاعدة التبعية: التبعيات تتجه إلى الداخل نحو Domain. يمكنك البدء بثلاث وإضافة طبقات مع نمو المشروع.

هل تزيد Clean Architecture من حجم الكود؟

نعم — بنسبة 30–50% مقارنة بـ MVVM بسبب استخراج واجهات المستودع وحالات الاستخدام والمحولات. هذا مفرط لتطبيق CRUD بسيط. Clean Architecture مبررة للمشاريع ذات منطق الأعمال المعقد حيث قابلية الاختبار وعزل الطبقات أهم من سرعة التطوير. للنماذج الأولية أو MVP استخدم MVVM — Clean Architecture ستبطئ البداية.

هل يمكن الجمع بين Clean Architecture وMVI؟

نعم، هذه ممارسة شائعة. تبقى حالات الاستخدام في Domain، بينما تستخدم Presentation دورة MVI (Intent ← Reducer ← State). طبقة Data تبقى كما هي، Domain يبقى كما هو. MVI في Presentation يوفر حالة شاشة قابلة للتنبؤ، Clean Architecture توفر عزل منطق الأعمال. هذا المزيج يستخدم في المشاريع الكبيرة التي تضم عشرات المطورين.

هل أحتاج إلى حالات استخدام لكل طلب بيانات؟

حالة الاستخدام مطلوبة عندما تتضمن العملية قاعدة أعمال: تحقق، دمج بيانات من مصدرين، حساب، تسجيل، فحص أذونات الوصول. طلب getUser(id) البسيط بدون منطق إضافي يمكنه استدعاء المستودع مباشرة من عارضة البيانات. ومع ذلك، من أجل الاتساق المعماري، تنشئ العديد من الفرق حالة استخدام لكل طريقة عامة في المستودع — هذا يضيف 5–10% من الكود لكنه يبسط القراءة.

كيف نختبر Clean Architecture؟

Domain: اختبارات وحدة لحالات الاستخدام مع مستودع وهمي — Kotlin/Swift نقي بدون Android SDK. Data: اختبارات تكامل لـ RepositoryImpl مع DataSource وهمي/مزيف. Presentation: اختبارات عارضة البيانات مع UseCase وهمي. بفضل قاعدة التبعية، تُختبر كل طبقة بشكل منفصل. في IT Sectr، تغطية Domain تصل إلى 95%، Data — 70–80%، Presentation — 60–70%.

الملخص

  • Clean Architecture — ثلاث طبقات (Domain وData وPresentation) مع قاعدة التبعية تتجه إلى الداخل
  • قاعدة التبعية — Domain لا يعرف شيئاً عن Data وPresentation، العزل عبر الواجهات
  • Domain — الكيانات وحالات الاستخدام وواجهات المستودع — Kotlin/Swift نقي بدون أطر
  • Data — RepositoryImpl وDataSources (شبكة، قاعدة بيانات، تخزين مؤقت) — تنفيذ واجهات Domain
  • Presentation — عارضات البيانات والواجهات — عرض فقط، منطق الأعمال في حالات الاستخدام
  • الاختبار — Domain مغطى باختبارات الوحدة بنسبة 90–95%
  • KMP — Clean Architecture مع Kotlin Multiplatform يعطي 60–80% كود مشترك iOS + Android

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا