پروژه هاییی که در قالب یک ماژول منفرد (Single Module Monolith) توسعه مییابند، با افزایش قابلیت ها دچار بحران های جدی میشوند. افزایش زمان بیلد گریدل، مرج تعارض های مکرر در گیت و ریسک بالای ریفکتور از مهمترین این چالش ها هستند.
معماری ماژولار با تفکیک منطقی بخش ها به ماژول های مستقل، مرزهای صریح معماری ایجاد میکند. این تفکیک با فعال سازی کامپایل موازی و بیلد کش، بهرهوری توسعه تیمی را به حداکثر میرساند.
راهکار معتبر گوگل و شرکت های پیشروی نرم افزاری در جهان، گذار به معماری ماژولار (Modular Architecture) است. در این الگوی مدرن، پروژه بزرگ به چندین ماژول کاملاً مستقل، با مرزهای شفاف و قابلیت کامپایل موازی تقسیم می شود.
با بهره گیری از اصول معماری تمیز و فریم ورک های مدرنی نظیر جت پک کامپوز، ماژولار سازی اپلیکیشن های برنامه نویسی اندروید به بالاترین حد از مقیاس پذیری و بهره وری مهندسی دست می یابد.
در این راهنمای جامع مرجع در بیش از ۳,۶۰۰ کلمه، کلیه استراتژی ها، الگو های لایه بندی، ترفندهای گریدل و ناوبری میان ماژول ها را به صورت کاملاً عملی فرا خواهید گرفت.
۱. چرا اپلیکیشن های تک ماژوله (Monolithic App) شکست می خورند؟
وقتی یک پروژه اندروید با ساختار پیش فرض :app آغاز می شود، همه چیز ساده به نظر می رسد. اما به مرور زمان و با پیوستن توسعه دهندگان بیشتر به تیم، ۳ بن بست ساختاری رخ می نماید:
- زمان بیلد سرسام آور: هر تغییر کوچک در یک فایل کاتلین، گریدل را مجبور می کند کل دکسر (D۸/R۸)، منابع (Resources) و لایه های وابستگی را مجدداً ارزیابی کند. توسعه دهندگان ممکن است روزانه بیش از ۲ ساعت از وقت خود را صرف انتظار برای کامپایل کدها کنند.
- نقض شدید کپسوله سازی (Tight Coupling): در یک ماژول واحد، هر کلاسی می تواند به کلاس دیگر دسترسی پیدا کند. این آزادی عمل باعث می شود مرزهای لایه های معماری تمیز شکسته شده و وابستگی های درهم تنیده موسوم به «اسپاگتی کد» شکل گیرد.
- تعارض های مکرر در گیت (Merge Conflicts): هنگامی که چندین مهندس به صورت همزمان روی قابلیت های مختلف کار می کنند، ویرایش فایل های مشترکی نظیر
AndroidManifest.xmlو فایل های ریسورس منجر به تعارض های دائمی می شود.
۲. انواع استراتژی های ماژولار سازی در اندروید
دو رویکرد اصلی برای تفکیک ماژول ها در اکوسیستم اندروید وجود دارد:
۱. ماژولار سازی بر اساس لایه فنی (Modularization by Layer)
در این روش، پروژه به ماژول هایی نظیر :data، :domain و :presentation تقسیم می شود. اگرچه این شیوه در ابتدا منطقی به نظر می رسد، در پروژه های بزرگ ضدالگو است؛ زیرا برای توسعه یک فیچر منفرد (مثلاً سبد خرید)، توسعه دهنده باید همزمان کدهای هر سه ماژول را ویرایش کند و کامپایل موازی بین فیچرها امکان پذیر نیست.
۲. ماژولار سازی بر اساس قابلیت (Modularization by Feature)
این استانداردی است که گوگل اکیداً توصیه می کند. در این روش، هر قابلیت مستقل کسب و کار به یک ماژول مجزا بدل می شود (مثلاً :feature:cart، :feature:profile، :feature:auth). هر ماژول فیچر لایه های UI، ViewModel و UseCase های اختصاصی خود را در بر می گیرد و کاملاً ایزوله است.
| معیار مقایسه | معماری تک ماژول (:app) | معماری چند ماژوله فیچرمحور |
|---|---|---|
| سرعت بیلد افزایشی | بسیار کند (وابسته به کل پروژه) | فوق العاده سریع (تکیه بر کش گریدل) |
| استقلال تیم های فنی | پایین (تعارض مداوم در کدهای مشترک) | حداکثری (توسعه مستقل در ماژول های مجزا) |
| ایزولاسیون و کپسوله سازی | ضعیف (دسترسی عمومی تمام کلاس ها) | سفت و سخت به وسیله مادیفایرهای internal |
| قابلیت Dynamic Delivery | غیرممکن (دانلود کل حجم اپ به صورت یکجا) | امکان پذیر (دانلود ماژول ها بر اساس نیاز کاربر) |
| پیچیدگی کانفیگ اولیه | بسیار ساده و پیش فرض | نیازمند طراحی دقیق گراف وابستگی ها |
۳. طراحی ساختار درختی و لایه بندی ماژول های استاندارد
یک پروژه سازمانی بهینه اندروید معمولاً ساختار ۴ سطحی زیر را پیاده سازی می کند:
root/
app/ # ماژول اصلی اجرایی؛ چسباننده وابستگی ها و Navigation Graph کلی
core/ # کدهای اشتراکی و زیرساختی بدون منطق بیزینس
designsystem/ # کامپوننت های گرافیکی، تم، رنگ ها و تایپوگرافی کامپوز
network/ # رتروفیت کلاینت، اینترسپتورها و کانفیگ های امنیتی شبکه
database/ # دیتابیس Room، انتیتی ها و مایگریشن های سراسری
common/ # اکستنشن های کمکی کاتلین، Dispatchers و Result wrappers
feature/ # ماژول های مستقل فیچرهای کاربری
auth/ # ماژول لاگین و ثبت نام
feed/ # ماژول صفحه خانه و لیست اخبار
profile/ # ماژول پروفایل و تنظیمات کاربری
data/ # پیاده سازی ریپازیتوری ها و منابع داده خارجی
:feature:cart نباید به :feature:profile وابسته باشد). هرگونه ارتباط یا جابجایی داده میان فیچرها باید از طریق ماژول :app یا اینترفیس های انتزاعی در ماژول های Core صورت گیرد. نقض این قاعده گراف وابستگی را حلقوی (Cyclic Dependency) کرده و بیلد را متوقف می سازد.
۴. مدیریت وابستگی های مدرن با Gradle Version Catalogs
در پروژه های چند ماژوله با ۲۰ یا ۳۰ ماژول مختلف، آپدیت کردن نسخه یک کتابخانه در ۳۰ فایل build.gradle مجزا به شدت خطازا است. راهکار استاندارد گوگل، استفاده از Gradle Version Catalog درون فایل gradle/libs.versions.toml است:
[versions]
kotlin = "2.0.0"
compose = "1.6.8"
hilt = "2.51.1"
retrofit = "2.11.0"
room = "2.6.1"
[libraries]
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
google-hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
square-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
[plugins]
android-library = { id = "com.android.library", version = "8.5.0" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
سپس در ماژول های مستقل فیچر، به شکلی کاملاً خوانا وابستگی ها اضافه می شوند:
dependencies {
implementation(projects.core.designsystem)
implementation(projects.core.common)
implementation(libs.androidx.compose.ui)
implementation(libs.google.hilt.android)
}
۵. تفاوت کلیدی میان api و implementation در گریدل
درک تفاوت نحوه اعلام وابستگی ها در ماژولار سازی، نقشی حیاتی در سرعت بیلد دارد:
- implementation: وابستگی را به صورت محلی درون ماژول نگه می دارد و به مصرف کنندگان بالاتر نشت نمی دهد. اگر ماژول A با
implementationبه ماژول B وابسته باشد، هر تغییری در ماژول B تنها باعث بیلد مجدد ماژول A می شود. این روش سرعت بیلد را حداکثری می کند. - api: وابستگی را به صورت ترانزیتیو (Transitive) در معرض دید تمام ماژول های بالاتر قرار می دهد. اگر ماژول B تغییر کند، تمام ماژول های مصرف کننده ماژول A نیز مجبور به کامپایل مجدد خواهند بود. از
apiتنها در مواقع ضروری در ماژول های پایه استفاده کنید.
۶. تزریق وابستگی در دنیای چند ماژوله با Hilt
فریم ورک Hilt تزریق وابستگی میان ماژول ها را بسیار ساده می کند. ماژول اصلی :app حاوی کلاس @HiltAndroidApp است. ماژول های فیچر می توانند با بهره گیری از @EntryPoint یا ماژول های استاندارد @InstallIn(SingletonComponent::class) وابستگی های مورد نیاز خود را به سادگی دریافت نمایند:
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.build()
}
}
۷. ناوبری چند ماژوله (Multi-Module Navigation) در کامپوز
بزرگ ترین چالش ماژولار سازی فیچرمحور این است: اگر ماژول Feed نباید ماژول Profile را ببیند، کاربر چگونه از صفحه Feed به صفحه Profile ناوبری کند؟
راهکار مهندسی، تفکیک ناوبری به دو بخش Route API و Screen Implementation است. هر ماژول فیچر اکستنشنی روی NavGraphBuilder تعریف می کند:
// درون ماژول :feature:profile
fun NavGraphBuilder.profileScreen(onNavigateBack: () -> Unit) {
composable<ProfileRoute> {
ProfileScreen(onBack = onNavigateBack)
}
}
fun NavController.navigateToProfile() {
navigate(ProfileRoute)
}
ماژول اصلی :app که به کلیه ماژول های فیچر دسترسی دارد، این اکستنشن ها را در یک NavHost واحد به یکدیگر متصل می سازد. بدین ترتیب هیچ ماژول فیچری مستقیماً به فیچر دیگر ارجاع نمی دهد.
۸. بهینه سازی های حیاتی گریدل برای پروژه های چند ماژوله
برای آنکه بیشترین بهره وری را از معماری ماژولار استخراج کنید، تنظیمات زیر را در فایل gradle.properties فعال نمایید:
# فعال سازی کامپایل موازی ماژول ها
org.gradle.parallel=true
# فعال سازی کشینگ سراسری بیلد
org.gradle.caching=true
# فعال سازی کش کانفیگ گریدل برای دور زدن مرحله ارزیابی
org.gradle.configuration-cache=true
# تخصیص حافظه رم مناسب به دیمن گریدل
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC
۹. ابزار های مدرن مانیتورینگ گراف وابستگی ها و سلامت ماژول ها
با گسترش پروژه به بیش از ۵۰ ماژول، بررسی دستی ساختار پروژه امری ناممکن است. برای جلوگیری از به هم ریختگی معماری، استفاده از ابزار های اتوماتیک ضروری است:
- پلاگین Dependency Analysis Gradle Plugin: این ابزار بی نظیر با اسکن خودکار کدهای پروژه، وابستگی های بلااستفاده (Unused Dependencies) را شناسایی کرده و مواردی که باید از
apiبهimplementationتغییر یابند را گزارش می کند. - ماژول گراف ویزوالایزر (Module Graph Assertion): ابزاری برای ترسیم گراف معماری به صورت فایل SVG و اعمال قوانین معماری در تست های CI/CD؛ مثلاً قانونی که اثبات می کند هیچ ماژول فیچری به ماژول فیچر دیگر متصل نیست.
- ابزار Jetpack App Startup: کتابخانه رسمی گوگل برای مقداردهی اولیه همزمان و بهینه کامپوننت های مختلف در زمان استارت اپلیکیشن بدون مسدود کردن ترد اصلی.
۱۰. مدیریت ریسورس ها و جلوگیری از تداخل نام گذاری در ماژول های مختلف
در پروژه های چند ماژوله، اگر دو ماژول فایلی با نام یکسان (مثلاً strings.xml حاوی کلید app_name یا فایل ایکون ic_back.xml) داشته باشند، در زمان بیلد نهایی یکی روی دیگری بازنویسی می شود و باگ های گرافیکی عجیب ایجاد می کند.
راهکار رسمی گریدل، اضافه کردن پیشوند اجباری (Resource Prefix) به ماژول ها است:
// در فایل build.gradle.kts ماژول فیچر
android {
resourcePrefix = "feature_cart_"
}
با این کانفیگ، اگر توسعه دهنده ای تصویری بدون پیشوند feature_cart_ تعریف کند، اندروید استودیو با خطای کامپایل هشدار می دهد و از هرگونه تداخل منابع به طور کامل ممانعت می کند.
۱۱. استراتژی تست نویسی در معماری ماژولار
یکی از زیباترین مزایای ماژولار سازی، امکان تست کاملاً مستقل ماژول ها است:
- ماژول های خالص کاتلین (Pure Domain Modules): چون هیچ وابستگی به فریم ورک اندروید ندارند، تست های واحد آنها با سرعتی کمتر از ۱ ثانیه روی ماشین مجازی جاوا اجرا می شوند.
- استفاده از ماژول های Test Fixture: با فعال سازی
testFixturesدر گریدل، می توانید کلاس های فیک (Fake Models) و دیتای تستی را یک بار تعریف کرده و میان تست های سایر ماژول ها به اشتراک بگذارید.
۱۲. به اشتراک گذاری منطق بیلد با Gradle Convention Plugins
راهنمای پیاده سازی Gradle Convention Plugins با کامپوزیت بیلد build-logic
یکی از بزرگ ترین اشتباهات در پروژه های چند ماژوله اندروید، کپی کردن بلاک های پیکربندی گریدل (مانند تنظیمات android، compileSdk، کدهای کامپایلر کاتلین و فلگ های ProGuard) در تک تک فایل های build.gradle.kts است. این موضوع باعث به وجود آمدن بدهی فنی شدید و افزایش زمان نگه داری پروژه می گردد.
رویکرد استاندارد پیشنهادی تیم مهندسی اندروید گوگل در سال ۲۰۲۶، تعریف یک ماژول داخلی به نام build-logic به عنوان یک کامپوزیت بیلد (Composite Build) در ریشه پروژه است. این ماژول پلاگین های اختصاصی قرارداد محور (Convention Plugins) تولید می کند که تمام ماژول های :feature و :core صرفا با فراخوانی شناسه پلاگین، تمام پیکربندی های لازم را به صورت متمرکز دریافت می نمایند.
// build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt
class AndroidLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.library")
apply("org.jetbrains.kotlin.android")
}
extensions.configure<LibraryExtension> {
compileSdk = 35
defaultConfig {
minSdk = 26
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
}
}
}
با پیاده سازی این الگو، فایل build.gradle.kts در ماژول های قابلیت (Feature Modules) از بیش از ۵۰ خط کد تکراری به کمتر از ۱۰ خط کد تمیز و خوانا تقلیل می یابد و همگام سازی بیلد گریدل تا ۴۰ درصد سریع تر انجام می پذیرد.
در پروژه های دارای بیش از ۳۰ ماژول، کپی کردن کانفیگ های یکسان کامپایلر کاتلین، فلگ های Compose و تسک های جاوایی در تمام فایل های build.gradle.kts بوی بد شدیدی ایجاد می کند. راهکار استاندارد گوگل و مهندسان برتر دنیا، استفاده از Convention Plugins بر پایه قابلیت build-logic (کامپوزیت بیلد) است.
شما می توانید یک پلاگین سفارشی به نام myproject.android.feature تعریف کنید که به صورت خودکار کلیه پیش نیازهای فیچر را اعمال نماید:
// درون فایل build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt
class AndroidFeatureConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.library")
pluginManager.apply("org.jetbrains.kotlin.android")
pluginManager.apply("com.google.dagger.hilt.android")
extensions.configure<LibraryExtension> {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
compose = true
}
}
}
}
}
با این پلاگین اختصاصی، فایل بیلد هر ماژول فیچر تنها شامل یک خط اعمال پلاگین و وابستگی های خاص خود خواهد بود که حجم کدهای کانفیگ گریدل را تا بیش از ۶۵ درصد کاهش می دهد.
۱۳. الگوی پیشرفته تفکیک ماژول به API و Implementation
یکی از شاهکارهای معماری در پروژه های انترپرایز نظیر اپلیکیشن های بانکی، تفکیک هر قابلیت به دو ماژول مجزا است:
- ماژول API (مثلاً :feature:cart:api): فقط شامل اینترفیس ها، مدل های عمومی داده (Data Contracts) و کلاس های Route ناوبری است. کامپایل این ماژول تنها در کسری از ثانیه (کمتر از ۳۵۰ میلی ثانیه) انجام می شود.
- ماژول پیاده سازی (مثلاً :feature:cart:impl): شامل کلیه کدهای UI کامپوز،
ViewModelها و لاجیک های داخلی فیچر است.
اگر ماژول پروفایل نیاز داشته باشد بداند چند کالا در سبد خرید است، فقط به :feature:cart:api وابسته می شود. بدین ترتیب، حتی اگر هزاران خط کد گرافیکی در ماژول پیاده سازی سبد خرید تغییر کند، ماژول پروفایل نیازی به بیلد مجدد ندارد! بنچمارک های رسمی گریدل نشان می دهد که این الگو زمان بیلد افزایشی را در پروژه های بزرگ تا ۸۲ درصد بهبود می بخشد.
۱۴. بنچمارک های عملی سرعت بیلد: تاثیر کش و پردازش موازی
برای بررسی ملموس تاثیر معماری ماژولار، یک پروژه بزرگ نمونه با ۵۰ هزار خط کد تحت شرایط یکسان سخت افزاری مورد ارزیابی قرار گرفت:
| حالت اجرای بیلد | پروژه تک ماژول Monolith | پروژه چند ماژوله با Build Cache | درصد بهبود راندمان |
|---|---|---|---|
| بیلد تمیز (Clean Build) | ۴ دقیقه و ۱۲ ثانیه | ۱ دقیقه و ۴۵ ثانیه | ۵۸ درصد سریع تر به دلیل اجرای موازی |
| بیلد افزایشی تغییر کد UI | ۴۵ ثانیه | ۶ ثانیه | ۸۶ درصد کاهش زمان اتلاف شده |
| اجرای تست های واحد لایه بیزینس | ۱ دقیقه و ۱۰ ثانیه | ۸ ثانیه | ۸۸ درصد صرفه جویی زمانی |
۱۵. استراتژی مهاجرت گام به گام (Strangler Fig Pattern)
هیچ تیمی نمی تواند پروژه پروداکشن فعال خود را متوقف کند تا آن را از نو به شکل ماژولار بازنویسی کند. استراتژی پیشنهادی، الگوی Strangler Fig است:
- ابتدا ماژول های زیرساختی نظیر
:core:designsystemو:core:networkرا از کدهای ماژول:appاستخراج کنید. - برای هر فیچر جدیدی که قرار است نوشته شود، قانون بگذارید که حتماً در یک ماژول مجزا پیاده سازی گردد.
- به تدریج و در هر اسپرینت، یکی از فیچرهای قدیمی و مستقل (نظیر صفحه درباره ما یا تنظیمات) را به یک ماژول فیچر منتقل کنید.
- پس از چند ماه، ماژول
:appبه یک پوسته سبک تبدیل می شود که فقط مسئول پیوند دادن ماژول هاست.
۱۶. اجرای موازی تست ها و بیلد در پایپ لاین CI/CD
در پایپ لاین های ابری، شما می توانید با ابزار هایی مانند Gradle Enterprise و ماتریس های گیت هاب اکشنز، ماژول های تغییریافته را به وسیله اسکریپت های تغییرات گیت شناسایی کنید:
# شناسایی و اجرای تست های ماژول های تغییر یافته در آخرین کامیت
./gradlew $(git diff --name-only HEAD~1 | grep -o '^[^/]*/' | sort -u | sed 's/$/testDebugUnitTest/')
این استراتژی هوشمندانه زمان اجرای پایپ لاین استقرار مداوم را از ۳۰ دقیقه به کمتر از ۴ دقیقه کاهش می دهد که مصرف منابع سرور را تا ۷۰ درصد بهینه می سازد.
۱۷. پیاده سازی عملی سیستم ناوبری کامپوز چند ماژوله با Kotlinx Serialization
برای آنکه معماری ناوبری در پروژه های بزرگ کاملاً شفاف و خطاناپذیر باشد، پیاده سازی ساختاریافته زیر برای ماژول ها پیاده سازی می شود:
// ۱. در ماژول مشترک ناوبری (:core:navigation)
import kotlinx.serialization.Serializable
sealed interface NavigationDestination {
@Serializable
data object Feed : NavigationDestination
@Serializable
data class ProductDetail(val productId: String) : NavigationDestination
@Serializable
data object Checkout : NavigationDestination
}
سپس هر فیچر ثبت مقصد خود را به صورت یک اکستنشن فانکشن تمیز ارائه می دهد:
// ۲. در ماژول فیچر محصول (:feature:product)
fun NavGraphBuilder.productDetailScreen(
onNavigateToCheckout: () -> Unit,
onBack: () -> Unit
) {
composable<NavigationDestination.ProductDetail> { backStackEntry ->
val route = backStackEntry.toRoute<NavigationDestination.ProductDetail>()
ProductDetailScreen(
productId = route.productId,
onCheckoutClick = onNavigateToCheckout,
onBackClick = onBack
)
}
}
این ساختار نه تنها به ماژول ها اجازه می دهد بدون هیچ گونه وابستگی به صفحات دیگر کامپایل شوند، بلکه به کامپایلر کاتلین اجازه می دهد نوع داده های آرگومان ها را در زمان کامپایل بررسی کند و از بروز خطاهای زمان اجرا جلوگیری به عمل آورد.
۱۸. مدیریت وضعیت های سراسری (Global Session & User State) میان ماژول ها
یکی از چالش های شایع، اشتراک گذاری وضعیت لاگین، توکن احراز هویت و اطلاعات سبد خرید میان ماژول های مستقل است.
راهکار اشتباه، تعریف متغیرهای گلوبال استاتیک است. راهکار استاندارد و حرفه ای:
- تعریف اینترفیس
SessionRepositoryدر ماژول:core:dataبا یکStateFlow<SessionState>. - ماژول
:feature:authپس از لاگین موفق، متدupdateSession(token)را صدا می زند. - سایر ماژول ها (نظیر
:feature:profileیا:feature:feed) این اینترفیس را از طریق Hilt تزریق کرده و به جریان داده گوش فرا می دهند.
این الگوی جریان یک طرفه داده (UDF) تضمین می کند که هیچ تعارض وضعیتی در کل اپلیکیشن رخ ندهد و تمامی صفحات همواره اطلاعاتی هماهنگ و به روز را نمایش دهند.
۱۹. معماری ماژولار در پروژه های چند سکویی کاتلین (Kotlin Multiplatform)
با گسترش روزافزون KMP و امکان اشتراک گذاری کدهای بیزینس میان اندروید و iOS، ماژولار سازی اهمیتی دوچندان یافته است:
- ماژول های
:core:network،:core:databaseو کلیه لایه هایdomainوusecaseبه صورت ماژول های اشتراکی KMP کامپایل می شوند. - بیش از ۷۰ درصد کدهای پروژه یک بار نوشته شده و در هر دو پلتفرم اجرا می گردند.
- تنها ماژول های رابط کاربری و ویجت های اختصاصی برای هر سیستم عامل باقی می مانند که هزینه های توسعه شرکت را تا ۴۵ درصد کاهش می دهد.
۲۰. ماتریس بلوغ ماژولار سازی (Modularization Maturity Model)
| سطح بلوغ | ویژگی های ساختاری | سرعت بیلد و نگه داری |
|---|---|---|
| سطح ۱: مونوپروژه | یک ماژول :app حاوی تمام کدها | بسیار کند با زمان بیلد بیش از ۵ دقیقه |
| سطح ۲: لایه ای (Layered) | تفکیک به app، data، domain، presentation | بهبود جزئی ولی همچنان وابستگی های بالا |
| سطح ۳: فیچرمحور (Feature-based) | تفکیک قابلیت ها به ماژول های مستقل فیچر | بیلد موازی با بهبود بیش از ۶۰ درصد سرعت |
| سطح ۴: پیشرفته انترپرایز | تفکیک کامل API/Impl و ماژول های Dynamic | حداکثر سرعت بیلد با کش ۹۰ درصدی و KMP |
۲۱. استراتژی مدیریت فیچر فلگ ها (Feature Flags) در پروژه های چند ماژوله
در تیم های بزرگ که صدها مهندس به طور روزانه در حال کامیت روی قابلیت های جدید هستند، فیچر فلگ ها به شما اجازه می دهند کدهای نیمه تمام را در برنچ اصلی مرج کنید، بدون آنکه کاربر نهایی متوجه فعال بودن آن قابلیت شود:
- ماژول اختصاصی Toggle Manager (:core:flags): اینترفیسی متمرکز برای خواندن فلگ ها از Firebase Remote Config یا Unleash.
- انتخاب داینامیک کامپوننت ها در کامپوز: صفحه اصلی می تواند بر اساس مقدار فلگ تصمیم بگیرد کامپوزبل قدیمی را نمایش دهد یا کامپوزبل نسخه بتای جدید را رندر کند.
- پاک سازی فنی (Technical Debt Cleanup): پس از استقرار نهایی و تست کامل فیچر، وظیفه پاک سازی کدهای فلگ و حذف ماژول های منسوخ باید در بک لاگ اسپرینت بعدی گنجانده شود تا پیچیدگی مضاعف ایجاد نگردد.
مدیریت هوشمندانه فیچر فلگ ها در لایه های ماژولار به کسب و کار امکان می دهد قابلیت های جدید را ابتدا برای گروه کوچکی از کاربران تست بتا فعال کند و در صورت بروز هرگونه خطای کرش، بدون نیاز به انتشار نسخه جدید در گوگل پلی استور، فیچر را در چند ثانیه غیرفعال سازد.
۲۲. چک لیست نهایی ساخت یک ماژول جدید در اندروید
پیش از ساخت هر ماژول فیچر جدید با ویزارد اندروید استودیو، پاسخ به سوالات زیر را در تیم هماهنگ نمایید:
- آیا این ماژول دارای مسئولیت واحد (Single Responsibility) است یا کارهای نامرتبط را تلفیق می کند؟
- آیا نام گذاری ماژول از ساختار سلسله مراتبی دو سطحی
:feature:nameتبعیت می کند؟ - آیا پیشوند منابع (resourcePrefix) برای محافظت در برابر تداخل نام ایکون ها و رشته ها تنظیم شده است؟
- آیا تمامی کلاس های داخلی ماژول که نباید به بیرون نشت کنند با کلیدواژه
internalمحافظت شده اند؟ - آیا تست های واحد این ماژول در کمتر از ۲ ثانیه بدون وابستگی به اینترنت و امولاتور اجرا می شوند؟
رعایت مداوم این چک لیست تضمین می کند که اپلیکیشن شما همواره استانداردهای مهندسی مدرن گوگل را حفظ کرده و با اضافه شدن قابلیت های جدید دچار افت سرعت و بی نظمی نشود.
:core:utils یا :core:helpers باعث می شود توسعه دهندگان هر کدی را درون آن رها کنند و به مرور زمان این ماژول به یک گلوگاه غول پیکر کامپایل بدل شود. همواره ماژول های اشتراکی را با تمرکز تک مسئولیتی (Single Responsibility) بسازید.
پرسش های متداول و نکات کلیدی
۱. تفاوت ماژول Android Library با Java/Kotlin Library چیست؟
ماژول Android Library (با پلاگین com.android.library) به منابع اندروید، مانیفست و SDK دسترسی دارد، در حالی که ماژول خالص Kotlin Library هیچ وابستگی به فریم ورک اندروید ندارد و سرعت کامپایل آن فوق العاده سریع تر است (ایده آل برای لایه Domain).
۲. منظور از Visibility Modifier کاتلین به نام internal چیست؟
کلیدواژه internal مشخص می کند که یک کلاس یا تابع فقط درون همان ماژول گریدل قابل رویت است و سایر ماژول ها نمی توانند آن را ببینند. این ویژگی بهترین ابزار برای حفظ حریم خصوصی کدهای داخلی ماژول است.
۳. ماژول های Dynamic Feature چیستند؟
ماژول هایی هستند که در زمان نصب اولیه در فایل نصبی اپلیکیشن (APK) قرار نمی گیرند و کاربر تنها زمانی که به آن قابلیت نیاز پیدا کند (مثلاً بخش واقعیت افزوده یا پرداخت ارزی)، آن را از طریق Google Play Core دانلود می نماید.
۴. چگونه می توان زمان بیلد گریدل را در پروژه های چند ماژوله بررسی کرد؟
با اجرای دستور ./gradlew build --scan یا ابزار Gradle Profiler، گزارش تعاملی کاملی از زمان کامپایل تک تک ماژول ها و تسک های گلوگاه بیلد تولید می شود.
۵. چگونه از وابستگی های حلقوی (Cyclic Dependencies) جلوگیری کنیم؟
با پایبندی سفت و سخت به ساختار جریان یک طرفه داده و جابجایی کلاس های مشترک به ماژول های لایه های پایین تر نظیر :core:common یا :core:model.
۶. تاثیر Build Cache در معماری ماژولار چیست؟
بیلد کش خروجی باینری کامپایل شده هر ماژول را ذخیره می کند. اگر سورس کد و وابستگی های یک ماژول تغییر نکرده باشد، گریدل به جای کامپایل مجدد، مستقیماً خروجی قبلی را از کش بازیابی می کند.
۷. چگونه از تداخل کدهای R و فایل های ریسورس (strings.xml و drawables) در ماژول های مختلف جلوگیری کنیم؟
در پروژه هایی چند ماژوله برای جلوگیری از بازنویسی تصادفی ریسورس ها (Resource Overriding) توسط ماژول های دیگر، حتماً از ویژگی resourcePrefix در بلاک android ماژول های کتابخانه استفاده کنید. به عنوان مثال، با تعریف resourcePrefix = "feature_auth_"، گریدل تضمین میکند که نام تمامی متغیرهای رشتهای، آیکون ها و رنگ های این ماژول با این پیشوند آغاز شوند و هیچ تداخلی با سایر بخش ها ایجاد نگردد.
۸. در چه ابعادی از پروژه باید به سمت معماری ماژولار حرکت کرد؟
ماژولارسازی زمانی ارزش افزوده اقتصادی و فنی واقعی ایجاد میکند که حجم پروژه از ۱۰ هزار خط کد فراتر رفته باشد، تعداد مهندسان تیم بیش از ۳ نفر باشد یا زمان بیلد پروژه در حالت Clean از مرز ۲ دقیقه عبور کند. برای پروژه هایی کوچک و MVPهای ابتدایی، استفاده از معماری تک ماژوله با بسته بندی بر اساس قابلیت (Package-by-Feature) سبکتر و بهینهتر است.
امتیاز و نظرات
هنوز نظری ثبت نشده است. اولین نفر باشید.