Kotlin Academy · บทเรียน

การแชร์ชั้น Repository และ Use Case

ย้ายตรรกะทางธุรกิจและการเข้าถึงข้อมูลไปยัง commonMain เพื่อใช้ซ้ำได้สูงสุด

บทเรียน 3 จาก 413 ขั้นตอน

การแชร์ชั้น Repository และ Use Case เป็นบทเรียน Kotlin Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Kotlin Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

เป้าหมายด้านสถาปัตยกรรม

ใน KMP เป้าหมายคือผลักตรรกะให้ได้มากที่สุดไปไว้ใน commonMain ได้แก่ โมเดลโดเมน อินเทอร์เฟซรีโพซิทอรี กรณีใช้งาน และ ViewModels โดยให้เหลือเฉพาะอินพุต/เอาต์พุตของแพลตฟอร์มในชุดซอร์สของแพลตฟอร์ม

// 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 (หลายแพลตฟอร์ม) ส่วนกลไก 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) }
    }
}

ชั้นกรณีใช้งาน

กรณีใช้งาน (ตัวประสานงาน) จะควบคุมการทำงานร่วมกันของรีโพซิทอรีและบรรจุตรรกะทางธุรกิจไว้ กรณีใช้งานเป็น 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 หรือคลาสแบบปิดผนึกจากกรณีใช้งาน เพื่อส่งต่อข้อผิดพลาดโดยไม่ให้ข้อยกเว้นข้ามขอบเขตระหว่างโค้ดที่ใช้ร่วมกันกับโค้ดแพลตฟอร์ม

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

ตรวจสอบความเข้าใจ

ในโปรเจกต์ KMP ที่มีโครงสร้างดี ชั้นใดไม่ควรมีการอิมพอร์ตเฉพาะแพลตฟอร์ม

สรุปทบทวน

วางโมเดลโดเมน อินเทอร์เฟซรีโพซิทอรี กรณีใช้งาน และ ViewModels ที่ใช้ร่วมกันไว้ใน commonMain ใช้ Ktor สำหรับเครือข่ายและ SQLDelight สำหรับฐานข้อมูล โดยฉีดกลไกของแพลตฟอร์มเข้าที่ชั้น DI ทดสอบตรรกะทางธุรกิจใน commonTest ด้วยรีโพซิทอรีจำลอง

เริ่มต้นได้ฟรี

เรียนรู้ Kotlin ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
51
บทเรียน
203

คำถามที่พบบ่อย

บทเรียน “การแชร์ชั้น Repository และ Use Case” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การแชร์ชั้น Repository และ Use Case” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Kotlin Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การแชร์ชั้น Repository และ Use Case”

ย้ายตรรกะทางธุรกิจและการเข้าถึงข้อมูลไปยัง commonMain เพื่อใช้ซ้ำได้สูงสุด คุณปฏิบัติ Kotlin Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Kotlin Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Kotlin Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “การแชร์ชั้น Repository และ Use Case” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Kotlin Academy นี้ได้ไหม

ได้ บทเรียน Kotlin Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. โครงสร้างโปรเจกต์ KMP: commonMain, androidMain, iosMain
  2. กลไก expect/actual สำหรับ API ของแพลตฟอร์ม
  3. การแชร์ชั้น Repository และ Use Case
  4. การฉีดการพึ่งพาใน KMP ด้วย Koin
← กลับไปที่ Kotlin Academy