การทำ Modularization บน Android ปี 2026: สถาปัตยกรรม Multi-Module และคำถามสัมภาษณ์

เชี่ยวชาญสถาปัตยกรรม multi-module ของ Android ด้วย convention plugins, Gradle version catalogs และ feature modules พร้อมคำถามสัมภาษณ์ที่พบบ่อยเกี่ยวกับกลยุทธ์ modularization

การทำ Modularization บน Android ปี 2026: สถาปัตยกรรม Multi-Module และคำถามสัมภาษณ์

การทำ Modularization บน Android เปลี่ยน codebase แบบ monolithic ให้กลายเป็นระบบที่ดูแลรักษาและขยายได้ง่าย ช่วยให้ทีมทำงานแบบขนานโดยไม่เกิดความขัดแย้งของโค้ด แอปพลิเคชัน Android ที่ทำ modularization อย่างดีมีเวลา build ที่เร็วขึ้น ทดสอบแยกส่วนได้ และกระบวนการ onboarding นักพัฒนาใหม่สั้นลง

ภาพรวมของ Modularization

Modularization แบ่งแอปพลิเคชัน Android ออกเป็น Gradle modules ที่เป็นอิสระและมีขอบเขตชัดเจน Low coupling ระหว่าง modules และ high cohesion ภายในแต่ละ module คือหลักการสำคัญ แอปอ้างอิง Now in Android ของ Google สาธิต pattern นี้ด้วยมากกว่า 40 modules

ทำไมสถาปัตยกรรม Multi-Module ถึงสำคัญสำหรับแอป Android

แอป Android ที่มี module เดียวจะ compile ใหม่ทั้งหมดเมื่อมีการเปลี่ยนแปลงเพียงบรรทัดเดียว การแก้ไข networking layer กระตุ้นการ compile UI ใหม่ นักพัฒนาสองคนที่แก้ไขฟีเจอร์ต่างกันสร้าง merge conflicts ในไฟล์ที่ใช้ร่วมกัน เวลา build แบบ incremental อาจยาวนานกว่า 4 นาที

สถาปัตยกรรม multi-module แก้ไขปัญหาเหล่านี้โดยตรง:

  • Build เร็วขึ้น: Gradle compile ใหม่เฉพาะ modules ที่ได้รับผลกระทบ การเปลี่ยนแปลงใน :feature:checkout ไม่กระทบ :feature:profile
  • พัฒนาแบบขนาน: ทีมเป็นเจ้าของ modules แยกกันพร้อม API ที่กำหนดไว้ merge conflicts ลดลงอย่างมาก
  • Testing แยกส่วน: Unit tests ทำงานบน module เดียวโดยไม่ต้องโหลดแอปทั้งหมด
  • นำโค้ดกลับมาใช้: Design system module :core:ui กลายเป็น library ที่ใช้ข้ามหลายแอปได้

คู่มือ modularization Android อย่างเป็นทางการ แนะนำโครงสร้าง modules ตามฟีเจอร์หรือ layer ที่เปลี่ยนแปลงด้วยกัน ตาม Single Responsibility Principle

ประเภท Module และความรับผิดชอบ

Repository Now in Android กำหนดการจำแนก module ที่พิสูจน์แล้วว่าขยายได้สำหรับแอประดับ enterprise

settings.gradle.kts - โครงสร้าง modulekotlin
include(":app")

// Core modules - utilities ที่ใช้ร่วมกัน
include(":core:common")
include(":core:data")
include(":core:database")
include(":core:datastore")
include(":core:designsystem")
include(":core:domain")
include(":core:model")
include(":core:network")
include(":core:ui")

// Feature modules - logic เฉพาะหน้าจอ
include(":feature:home")
include(":feature:search")
include(":feature:bookmarks")
include(":feature:settings")

App module: ประกอบด้วย MainActivity, การตั้งค่า navigation และ scaffolding ระดับแอป ขึ้นกับ feature modules ทั้งหมดและ core modules ที่จำเป็น

Core modules: ให้ฟังก์ชันการทำงานที่ใช้ร่วมกันข้ามฟีเจอร์ :core:network จัดการ API calls :core:database จัดการ Room entities :core:designsystem กำหนด visual components ของแอป

Feature modules: ห่อหุ้มหน้าจอเดียวหรือ user flow แต่ละ feature module ขึ้นกับเฉพาะ core modules ที่ต้องการ ไม่เคยขึ้นกับ feature modules อื่น

โครงสร้างนี้บังคับ dependencies ทิศทางเดียว: features ขึ้นกับ core และ app ขึ้นกับ features

Gradle Version Catalogs สำหรับการจัดการ Dependency

การจัดการ dependencies ข้าม 25+ modules โดยไม่มี version conflicts ต้องการการกำหนดค่าแบบรวมศูนย์ Gradle Version Catalogs แก้ปัญหานี้ด้วยไฟล์ libs.versions.toml เพียงไฟล์เดียว

toml
# gradle/libs.versions.toml
[versions]
agp = "8.7.0"
kotlin = "2.1.0"
compose-bom = "2026.09.00"
ksp = "2.1.0-1.0.29"
hilt = "2.54"
room = "2.7.0"
retrofit = "2.11.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.15.0" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }

[bundles]
compose = ["androidx-compose-ui", "androidx-compose-material3"]
room = ["room-runtime", "room-ktx"]

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

Modules จากนั้นอ้างอิง dependencies ด้วย type-safe accessors:

feature/home/build.gradle.ktskotlin
dependencies {
    implementation(libs.bundles.compose)
    implementation(libs.hilt.android)
    ksp(libs.hilt.compiler)
}

Gradle สร้าง accessors เหล่านี้ระหว่าง sync ทำให้ IDE autocompletion และ compile-time validation ทำงานได้

พร้อมที่จะพิชิตการสัมภาษณ์ Android แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Convention Plugins: การกำหนดค่า Build แบบ DRY

โปรเจกต์ที่มี 25 modules โดยไม่มี convention plugins จะทำซ้ำ compileSdk, minSdk, composeOptions และการใช้ plugin เหมือนกันในทุก build.gradle.kts Modules ต่างกันอาจใช้การกำหนดค่าต่างกันโดยไม่ตั้งใจ

Convention plugins รวมศูนย์ logic นี้ใน included build build-logic

build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.ktkotlin
import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure

class AndroidLibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            pluginManager.apply("com.android.library")
            pluginManager.apply("org.jetbrains.kotlin.android")

            extensions.configure<LibraryExtension> {
                compileSdk = 35
                defaultConfig {
                    minSdk = 26
                    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
                }
                compileOptions {
                    sourceCompatibility = JavaVersion.VERSION_17
                    targetCompatibility = JavaVersion.VERSION_17
                }
            }
        }
    }
}

ลงทะเบียน plugin ใน build-logic/convention/build.gradle.kts:

build-logic/convention/build.gradle.ktskotlin
plugins {
    `kotlin-dsl`
}

dependencies {
    compileOnly(libs.android.gradlePlugin)
    compileOnly(libs.kotlin.gradlePlugin)
}

gradlePlugin {
    plugins {
        register("androidLibrary") {
            id = "myapp.android.library"
            implementationClass = "AndroidLibraryConventionPlugin"
        }
        register("androidFeature") {
            id = "myapp.android.feature"
            implementationClass = "AndroidFeatureConventionPlugin"
        }
        register("androidHilt") {
            id = "myapp.android.hilt"
            implementationClass = "AndroidHiltConventionPlugin"
        }
    }
}

Feature modules ตอนนี้ต้องการการกำหนดค่าน้อยที่สุด:

feature/home/build.gradle.ktskotlin
plugins {
    alias(libs.plugins.myapp.android.feature)
    alias(libs.plugins.myapp.android.hilt)
}

dependencies {
    implementation(projects.core.data)
    implementation(projects.core.designsystem)
}

Build-logic ของ Now in Android จาก Google ใช้ pattern นี้เหมือนกันทุกประการ

รูปแบบการสื่อสารระหว่าง Module

Feature modules ต้องไม่ขึ้นต่อกันโดยตรง ข้อจำกัดนี้รักษา compilation แบบขนานและป้องกัน circular dependencies สาม patterns จัดการการสื่อสาร cross-feature

core/navigation/src/main/kotlin/Routes.ktkotlin
object Routes {
    const val HOME = "home"
    const val PRODUCT_DETAIL = "product/{productId}"
    const val CHECKOUT = "checkout"
    
    fun productDetail(productId: String) = "product/$productId"
}

Feature modules import routes จาก :core:navigation โดยไม่รู้ว่า module ไหน implement แต่ละ destination

Shared Domain Models

core/model/src/main/kotlin/Product.ktkotlin
data class Product(
    val id: String,
    val name: String,
    val price: BigDecimal,
    val imageUrl: String
)

ทั้ง :feature:catalog และ :feature:checkout ขึ้นกับ :core:model สำหรับ class Product

Event Bus หรือ Shared ViewModel

สำหรับการสื่อสาร runtime, shared event bus ใน :core:common หรือ scoped ViewModel จัดการ events ข้าม feature:

core/common/src/main/kotlin/CartEventBus.ktkotlin
object CartEventBus {
    private val _events = MutableSharedFlow<CartEvent>()
    val events: SharedFlow<CartEvent> = _events.asSharedFlow()
    
    suspend fun emit(event: CartEvent) {
        _events.emit(event)
    }
}

sealed interface CartEvent {
    data class ItemAdded(val productId: String) : CartEvent
    data class ItemRemoved(val productId: String) : CartEvent
}

คำถามสัมภาษณ์เกี่ยวกับ Modularization บน Android

Modularization ปรากฏบ่อยในการสัมภาษณ์ Android ระดับ senior ผู้สัมภาษณ์ประเมินความเข้าใจเกี่ยวกับ build systems, การตัดสินใจด้านสถาปัตยกรรม และ trade-offs ในทางปฏิบัติ

เคล็ดลับสัมภาษณ์

เมื่อพูดคุยเกี่ยวกับ modularization ในการสัมภาษณ์ ให้อ้างอิง metrics ที่เป็นรูปธรรม: การปรับปรุงเวลา build, velocity ของทีม หรือจำนวน modules เฉพาะจากโปรเจกต์ คำตอบที่เป็นนามธรรมเกี่ยวกับ "separation of concerns ที่ดีขึ้น" น่าเชื่อถือน้อยกว่า "เวลา build ลดจาก 4 นาทีเหลือ 45 วินาทีหลัง modularization"

Q: ความแตกต่างระหว่าง api และ implementation dependencies ใน Gradle คืออะไร?

implementation dependencies เป็น internal ของ module Consumers ของ module ไม่สามารถเข้าถึงได้ api dependencies ถูก expose แบบ transitive: ถ้า module A ใช้ api(libs.retrofit), module B ที่ขึ้นกับ A สามารถใช้ Retrofit classes ได้โดยตรง

กฎง่ายๆ: ใช้ implementation เป็นค่าเริ่มต้น ใช้ api เฉพาะเมื่อ dependency เป็นส่วนหนึ่งของ public API ของ module การใช้ api มากเกินไปทำลาย encapsulation และทำให้ builds ช้าลง

Q: จะจัดการ navigation ระหว่าง feature modules ที่ไม่ขึ้นต่อกันอย่างไร?

สร้าง module :core:navigation ที่มี route definitions และ navigation interfaces Feature modules ขึ้นกับ shared module นี้ App module เชื่อม destinations กับ implementations นี่ทำตาม dependency inversion principle: features ขึ้นกับ abstractions ไม่ใช่ concrete implementations

Q: Trade-offs ของการมี modules มากเกินไปคืออะไร?

แต่ละ module เพิ่ม overhead การกำหนดค่า Gradle: build files, resource processing, manifest merging เวลา sync เริ่มต้นเพิ่มขึ้น จุดที่เหมาะสมแตกต่างกันตามโปรเจกต์ แต่ 50+ modules เป็นเรื่องปกติในแอปขนาดใหญ่ ประโยชน์ (parallel builds, isolated tests, ownership ที่ชัดเจน) มักจะมากกว่า overhead สำหรับทีม 5+ developers

Q: Convention plugins แตกต่างจาก buildSrc อย่างไร?

การเปลี่ยนแปลง buildSrc กระตุ้น rebuild โปรเจกต์ทั้งหมด Convention plugins ใน included build compile ใหม่เฉพาะเมื่อ code ของ plugin เปลี่ยน ไม่ใช่เมื่อ modules ที่ใช้เปลี่ยน นี่ทำให้ convention plugins เร็วกว่าสำหรับการพัฒนาแบบ iterative นอกจากนี้ convention plugins สามารถ publish เป็น artifact แยกต่างหากเพื่อใช้ซ้ำข้าม repositories

Q: จะ test feature module แบบแยกส่วนได้อย่างไร?

Feature modules ขึ้นกับ core modules ผ่าน interfaces สร้าง fake implementations ใน test fixtures:

core/data/src/testFixtures/kotlin/FakeProductRepository.ktkotlin
class FakeProductRepository : ProductRepository {
    private val products = mutableListOf<Product>()
    
    override suspend fun getProducts(): List<Product> = products
    
    fun addProduct(product: Product) {
        products.add(product)
    }
}

Test ของ feature module inject fakes ผ่าน testing APIs ของ Hilt หรือ manual constructor injection

สำหรับการฝึกคำถามเพิ่มเติมเกี่ยวกับสถาปัตยกรรม Android ดู คำถามสัมภาษณ์สถาปัตยกรรม MVVM และ module dependency injection

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

สรุปสำหรับโปรเจกต์ Android Multi-Module

  • โครงสร้าง modules ตามฟีเจอร์ (:feature:home, :feature:checkout) และ layer (:core:data, :core:ui) Features ขึ้นกับ core ไม่เคยขึ้นต่อกัน
  • ใช้ Gradle Version Catalogs (libs.versions.toml) เพื่อรวมศูนย์ dependency versions Bundles รวม dependencies ที่เกี่ยวข้องสำหรับ build files ที่สะอาดขึ้น
  • Implement convention plugins ใน build-logic เพื่อกำจัดการกำหนดค่าซ้ำซ้อนข้าม modules การเปลี่ยน plugin ครั้งเดียวอัปเดต modules ที่ใช้ทั้งหมด
  • บังคับ dependencies ทิศทางเดียว: app ขึ้นกับ features, features ขึ้นกับ core เครื่องมือเช่น Dependency Guard สามารถทำให้ enforcement เป็นอัตโนมัติ
  • วัดประสิทธิภาพ build ก่อนและหลัง modularization ติดตามเวลา incremental build ไม่ใช่แค่ clean builds เพื่อยืนยันการปรับปรุง
  • รักษาขอบเขต module ให้คงที่ การปรับโครงสร้างบ่อยๆ ลบล้างประโยชน์ของ modularization
ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Android เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 17 กันยายน 2569

แชร์

บทความที่เกี่ยวข้อง