مشاركة طبقتَي Repository وUse Case
انقل منطق الأعمال والوصول إلى البيانات إلى commonMain لتحقيق أكبر قدر من إعادة الاستخدام.
مشاركة طبقتَي Repository وUse Case درس مجاني في Kotlin Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Kotlin Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Kotlin Academy 4 دروس في المجموع.
هدف البنية
في KMP، يتمثل الهدف في نقل أكبر قدر ممكن من المنطق إلى commonMain، بما في ذلك نماذج المجال، وواجهات المستودعات، وحالات الاستخدام، وViewModels. ولا يبقى في source sets الخاصة بالمنصة سوى عمليات الإدخال والإخراج الخاصة بها.
// Target architecture:
// commonMain:
// domain/ - data classes, interfaces
// data/ - repository implementations using shared clients
// usecase/ - business logic, composing repositories
// androidMain / iosMain:
// DI wiring, platform drivers (DB, network)طبقة المجال في commonMain
عرّف كيانات المجال وواجهات المستودعات في commonMain. ولا تستورد أي مكونات خاصة بالمنصة.
// commonMain/domain/User.kt
data class User(val id: String, val name: String, val email: String)
// commonMain/domain/UserRepository.kt
interface UserRepository {
suspend fun getUser(id: String): User?
suspend fun getAllUsers(): List<User>
suspend fun saveUser(user: User)
}تطبيق مستودع مشترك باستخدام Ktor
طبّق المستودع في commonMain باستخدام Ktor client متعدد المنصات. ويوفر كل نظام أساسي محرك HTTP الخاص به.
// commonMain/data/RemoteUserRepository.kt
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
class RemoteUserRepository(private val client: HttpClient) : UserRepository {
override suspend fun getUser(id: String): User? =
client.get("https://api.example.com/users/$id").body()
override suspend fun getAllUsers(): List<User> =
client.get("https://api.example.com/users").body()
override suspend fun saveUser(user: User) {
client.post("https://api.example.com/users") { setBody(user) }
}
}طبقة حالات الاستخدام
تنظم حالات الاستخدام، أو interactor، عمل المستودعات وتحتوي على منطق الأعمال. وهي مكتوبة بـKotlin خالصة ولا تعتمد على المنصة.
// commonMain/usecase/GetUserUseCase.kt
class GetUserUseCase(private val repo: UserRepository) {
suspend operator fun invoke(id: String): Result<User> =
runCatching { repo.getUser(id) ?: error("User not found: $id") }
}
// commonMain/usecase/SaveUserUseCase.kt
class SaveUserUseCase(private val repo: UserRepository) {
suspend operator fun invoke(user: User): Result<Unit> =
runCatching { repo.saveUser(user) }
}ViewModel مشترك
يمكن أن توجد ViewModels في commonMain باستخدام kotlinx-coroutines وStateFlow. ويستخدمها Android مباشرة، بينما يغلفها iOS داخل SwiftUI.
// commonMain/viewmodel/UserViewModel.kt
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
class UserViewModel(private val getUser: GetUserUseCase) {
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
private val _user = MutableStateFlow<User?>(null)
val user = _user.asStateFlow()
fun load(id: String) {
scope.launch { _user.value = getUser(id).getOrNull() }
}
fun clear() = scope.cancel()
}إعداد Ktor HttpClient
أنشئ Ktor HttpClient في commonMain مع إعدادات مشتركة. ويُمرّر المحرك، مثل OkHttp أو Darwin، أو يُنشأ على مستوى كل منصة.
// commonMain/network/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
fun createHttpClient(engine: io.ktor.client.engine.HttpClientEngine? = null) =
HttpClient(engine ?: io.ktor.client.engine.cio.CIO) {
install(ContentNegotiation) { json() }
}SQLDelight للتخزين المشترك
ينشئ SQLDelight استعلامات Kotlin آمنة من حيث النوع لجميع المنصات انطلاقًا من ملفات .sq الموجودة في commonMain. أما برنامج تشغيل قاعدة البيانات الفعلي، فخاص بالمنصة.
// commonMain/db/UserQueries.sq:
// CREATE TABLE User (id TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL);
// selectAll:
// SELECT * FROM User;
// Usage in commonMain:
// val db = UserDatabase(driver)
// val users = db.userQueries.selectAll().executeAsList()ربط DI الخاص بالمنصة
اربط جميع المكونات في وحدة DI خاصة بالمنصة. أما الواجهة فمشتركة، وتختلف وسائط المُنشئ فقط.
// androidMain:
val androidModule = module {
single { AndroidSqliteDriver(UserDatabase.Schema, androidContext(), "users.db") }
single { UserDatabase(get()) }
single<UserRepository> { RemoteUserRepository(get()) }
single { GetUserUseCase(get()) }
viewModel { UserViewModel(get()) }
}
// iosMain:
// Same pattern with NativeSqliteDriverاختبار حالات الاستخدام في commonTest
لا تعتمد حالات الاستخدام على المنصة، مما يجعل اختبارها كوحدات في commonTest باستخدام مستودع وهمي أمرًا سهلًا.
// commonTest/usecase/GetUserUseCaseTest.kt
import kotlin.test.Test
import kotlin.test.assertEquals
class FakeUserRepo : UserRepository {
override suspend fun getUser(id: String) = if (id == "1") User("1", "Alice", "a@b.com") else null
override suspend fun getAllUsers() = emptyList<User>()
override suspend fun saveUser(user: User) {}
}
class GetUserUseCaseTest {
private val useCase = GetUserUseCase(FakeUserRepo())
@Test
fun testGetExistingUser() = kotlinx.coroutines.runBlocking {
val result = useCase("1")
assertEquals("Alice", result.getOrNull()?.name)
}
}معالجة الأخطاء في حالات الاستخدام
استخدم Result أو قيم الإرجاع من نوع sealed class في حالات الاستخدام لنقل الأخطاء دون عبور الاستثناءات لحدود الشيفرة المشتركة والمنصة.
// commonMain:
sealed class UserResult {
data class Success(val user: User) : UserResult()
data class NotFound(val id: String) : UserResult()
data class Error(val cause: Throwable) : UserResult()
}
class SafeGetUser(private val repo: UserRepository) {
suspend operator fun invoke(id: String): UserResult = try {
val user = repo.getUser(id)
if (user != null) UserResult.Success(user) else UserResult.NotFound(id)
} catch (e: Exception) { UserResult.Error(e) }
}المشاركة دون التضحية بأساليب المنصة
شارك المنطق لا واجهة المستخدم. يستخدم Android Compose، بينما يستخدم iOS SwiftUI. ويراقب كلاهما حالة ViewModel المشتركة عبر عمليات ربط خاصة بالمنصة.
// Android (Compose):
// val user by viewModel.user.collectAsState()
// iOS (SwiftUI):
// @State var user = viewModel.user.value
// viewModel.load(id: "1")
// Shared ViewModel:
// _user.value = ... (commonMain)
fun main() { println("Share logic; use platform UI frameworks") }تحقق سريع
أي طبقة يجب ألا تحتوي على imports خاصة بالمنصة في مشروع KMP منظم جيدًا؟
مراجعة
ضع نماذج المجال، وواجهات المستودعات، وحالات الاستخدام، وViewModels المشتركة في commonMain. استخدم Ktor للشبكة وSQLDelight لقاعدة البيانات، مع تمرير محركات المنصة عند طبقة DI. واختبر منطق الأعمال في commonTest باستخدام مستودعات وهمية.
تعلم Kotlin مع معلم ذكاء اصطناعي — مجانًا
اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.
- الدورات
- 51
- الدروس
- 203
الأسئلة الشائعة
هل درس «مشاركة طبقتَي Repository وUse Case» مجاني؟
نعم — نص درس «مشاركة طبقتَي Repository وUse Case» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Kotlin Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Kotlin Academy 4 دروس في المجموع.
ماذا ستتعلم في «مشاركة طبقتَي Repository وUse Case»؟
انقل منطق الأعمال والوصول إلى البيانات إلى commonMain لتحقيق أكبر قدر من إعادة الاستخدام. تتمرن على Kotlin Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Kotlin Academy؟
لا تُشترط خبرة سابقة. Kotlin Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «مشاركة طبقتَي Repository وUse Case»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Kotlin Academy هذا؟
نعم. كل درس في Kotlin Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- بنية مشروع KMP: commonMain وandroidMain وiosMain
- آلية expect/actual لواجهات برمجة المنصات
- مشاركة طبقتَي Repository وUse Case
- حقن الاعتماديات في KMP باستخدام Koin