אין אסטרטגיית מודולריזציה אחת שמתאימה לכל הפרויקטים. בעקבות האופי הגמיש של Gradle, כולל מגבלות מעטות לארגן פרויקט. בדף הזה תמצאו סקירה כללית של כמה כללים כלליים דפוסים שאפשר ליישם כשמפתחים אפליקציות ל-Android עם מודולים מרובים.
לכידות גבוהה ועקרון צימוד נמוך
אחת הדרכים לאפיין Codebase מודולרי היא להשתמש בצימוד ולכידות. צימוד מודד את מידת המודולים תלויים זה בזה. לכידות, בהקשר הזה, מודדת את האופן שבו רכיבי מודול יחיד קשור מבחינה פונקציונלית. ככלל, צריך לשאוף צימוד נמוך ולכידות גבוהה:
- צימוד נמוך פירושו שהמודולים צריכים להיות בלתי תלויים ככל האפשר אחד את השני, כך שלשינויים במודול אחד תהיה השפעה אפסית או מינימלית למודולים אחרים. המודולים לא צריכים להיות בעלי ידע על הפעילות הפנימית של מודולים אחרים.
- לכידות גבוהה, כלומר המודולים צריכים להיות חלק מאוסף של קוד פועלת כמערכת. צריך להגדיר להם תחומי אחריות ברורים ולהישאר בהם בתוך גבולות של ידע מסוים בתחום. כדאי לשקול ספר דיגיטלי לדוגמה תרגום מכונה. ייתכן בלתי הולם של שילוב קוד שקשור לספר ולתשלום יחד באותו מודול, מכיוון שמדובר בשני דומיינים פונקציונליים שונים.
סוגי מודולים
הדרך שבה מארגנים את המודולים תלויה בעיקר בארכיטקטורה של האפליקציה. נמצא למטה הם כמה סוגים נפוצים של מודולים שאפשר להציג באפליקציה, ובהמשך ארכיטקטורת האפליקציות המומלצת.
מודולים של נתונים
מודול נתונים בדרך כלל מכיל מאגר, מקורות נתונים ומחלקות של מודלים. שלושת תחומי האחריות העיקריים של מודול נתונים הם:
- מכלולים את כל הנתונים והלוגיקה העסקית של דומיין מסוים: כל נתונים צריך להיות אחראי לטיפול בנתונים שמייצגים דומיין. השירות יכול לטפל בסוגים רבים של נתונים, כל עוד הם קשורים זה לזה.
- חשיפת המאגר כ-API חיצוני: ה-API הציבורי של הנתונים צריך להיות מאגר, כי הם אחראים לחשיפת הנתונים את שאר האפליקציות.
- הסתרת כל פרטי ההטמעה ומקורות הנתונים מבחוץ:
מקורות הנתונים צריכים להיות נגישים רק למאגרים מאותו המודול.
הם נשארים מוסתרים מבחוץ. אפשר לאכוף את הדרישה הזו באמצעות
מילת מפתח לחשיפה
private
אוinternal
.
![](https://developer.android.com/static/topic/modularization/images/2_data_modules.png?authuser=8&hl=he)
מודולים של תכונות
תכונה היא חלק מבודד בפונקציונליות של האפליקציה, שבדרך כלל תואם למסך או לסדרה של מסכים בעלי קשר הדוק, כמו הרשמה או תשלום . אם באפליקציה יש ניווט בסרגל התחתון, סביר להניח שכל יעד היא תכונה.
![](https://developer.android.com/static/topic/modularization/images/2_bottom_bar.png?authuser=8&hl=he)
התכונות משויכות למסכים או ליעדים באפליקציה שלכם. לכן,
סביר להניח שיש להם ממשק משתמש משויך ו-ViewModel
לטפל בלוגיקה
ו-State. תכונה אחת לא חייבת להיות מוגבלת לתצוגה אחת,
יעד הניווט. המודולים של התכונות תלויים במודולים של נתונים.
![](https://developer.android.com/static/topic/modularization/images/2_feature_modules.png?authuser=8&hl=he)
מודולים של אפליקציות
מודולים של אפליקציות הם נקודת כניסה לאפליקציה. הם תלויים בתכונה ובדרך כלל מספקים ניווט ברמה הבסיסית. ניתן להדר מודול אפליקציה אחד לכמה קבצים בינאריים שונים הודות לבניית וריאנטים.
![](https://developer.android.com/static/topic/modularization/images/2_demo_full_dep_graph.png?authuser=8&hl=he)
אם האפליקציה מטרגטת כמה סוגי מכשירים, כמו 'רכבים', 'לבושים' או 'טלוויזיה', צריך להגדיר מודול אפליקציה לכל אחד מהם. כך אפשר להיעזר בפעולות ספציפיות לפלטפורמות ספציפיות של יחסי התלות.
![](https://developer.android.com/static/topic/modularization/images/2_wear_dep_graph.png?authuser=8&hl=he)
מודולים נפוצים
מודולים נפוצים, הידועים גם כמודולים ליבה, מכילים קוד שמודולים אחרים משתמשים בו לעיתים קרובות. הם מצמצמים את היתירות ולא מייצגים שכבה ספציפית של של הארכיטקטורה של האפליקציה. דוגמאות למודולים נפוצים:
- מודול ממשק משתמש: אם אתם משתמשים ברכיבים מותאמים אישית בממשק המשתמש או במיתוג מורכב כדאי להקיף את אוסף הווידג'טים במודול כדי שכל התכונות יהיו זמינות לשימוש חוזר. זה יכול לעזור בשמירה על עקביות בממשק המשתמש לתכונות שונות. לדוגמה, אם נושאי הנושאים מרוכזים, אפשר להימנע ארגון מחדש כואב במהלך מיתוג מחדש.
- המודול של Analytics: בדרך כלל המעקב נקבע על סמך דרישות עסקיות עם מעט מאוד התייחסות לארכיטקטורת התוכנה. כלי המעקב של Analytics הם משמש לעיתים קרובות ברכיבים רבים שאינם קשורים זה לזה. אם זה קורה אצלכם, יכול להיות כדאי להשתמש במודול ייעודי לניתוח נתונים.
- מודול רשת: כאשר למודולים רבים נדרש חיבור לרשת, ייתכן כדאי לשקול הוספה של מודול ייעודי לאספקת לקוח http. זה כן שימושי במיוחד כשהלקוח שלך דורש הגדרות אישיות.
- מודול כלי עזר: כלי עזר, שנקראים גם כלי עזר, הם בדרך כלל חלקים קטנים של קוד שנעשה בו שימוש חוזר בכל האפליקציה. דוגמאות לכלי שירות: כלים מסייעים לבדיקות, פונקציה לעריכת המטבע, מאמת אימייל או .
מודולים לבדיקה
מודולים לבדיקה הם מודולים של Android שמשמשים למטרות בדיקה בלבד. המודולים מכילים קוד בדיקה, משאבי בדיקה ויחסי תלות של בדיקה, שנדרשות להפעלת בדיקות ואין בהן צורך במהלך זמן הריצה של האפליקציה. המודולים לבדיקה נוצרים כדי להפריד בין הקוד הספציפי לבדיקה לבין הקוד באופן שמקל על הניהול והתחזוקה של קוד המודול.
תרחישים לדוגמה למודולים לבדיקה
בדוגמאות הבאות אפשר לראות מצבים שבהם מטמיעים מודולים לבדיקה יכולות להיות שימושיות במיוחד:
קוד בדיקה משותף: אם יש לכם מספר מודולים בפרויקט וחלק קוד הבדיקה חל על יותר ממודול אחד, אפשר ליצור בדיקה מודול כדי לשתף את הקוד. כך תוכלו להפחית כפילויות ולבצע את הבדיקה קל יותר לתחזק. קוד בדיקה משותף יכול לכלול סוגי שירות או כמו טענות נכוֹנוּת (assertions) או התאמות בהתאמה אישית, וגם נתוני בדיקה, כמו סימולציות של תגובות JSON.
תצורה נקייה יותר של Build: המודולים לבדיקה מאפשרים שימוש נקי יותר את ההגדרות האישיות, מכיוון שיכול להיות להם קובץ
build.gradle
משלהם. לא מומלץ צריכים להעמיס את הקובץbuild.gradle
של מודול האפליקציה בהגדרות רלוונטיות לבדיקות בלבד.בדיקות שילוב: אפשר להשתמש במודולים של בדיקה לאחסון השילוב בדיקות שמשמשות לבדיקת אינטראקציות בין חלקים שונים באפליקציה, כולל ממשק משתמש, לוגיקה עסקית, בקשות רשת ושאילתות מסדי נתונים.
אפליקציות בקנה מידה גדול: מודולים של בדיקה שימושיים במיוחד אפליקציות בקנה מידה גדול עם בסיסי קוד מורכבים ומספר מודולים. בתרחישים כאלה במקרים מסוימים, המודולים לבדיקה יכולים לעזור בשיפור הארגון והתחזוקה של קוד.
![](https://developer.android.com/static/topic/modularization/images/2_test_modules.png?authuser=8&hl=he)
תקשורת של מודול ממודול
מודולים קיימים בהפרדה מלאה לעיתים רחוקות, ובדרך כלל מסתמכים על מודולים אחרים לתקשר איתם. חשוב לשמור על צימוד נמוך גם כשמודולים עובדים יחד ומחליפים מידע לעיתים קרובות. לפעמים ישיר בין שני יחידות לימוד אינה רצויה, ומגבלות של ארכיטקטורה. זה גם יכול להיות בלתי אפשרי, למשל במחזורים של יחסי התלות.
![](https://developer.android.com/static/topic/modularization/images/2_mediator.png?authuser=8&hl=he)
כדי להתגבר על הבעיה הזו, אפשר להשתמש בתיווך של מודול שלישי בין שני מודולים אחרים. המודול של המגשר יכול להאזין להודעות משני הסוגים של המודולים ולהעביר אותם לפי הצורך. באפליקציה לדוגמה שלנו, צריך לדעת איזה ספר לרכוש, למרות שהאירוע הגיע מסך נפרד שהוא חלק מתכונה אחרת. במקרה הזה, הפרמטר המגשר הוא המודול שתרשים הניווט נמצא בבעלותו (בדרך כלל מודול של אפליקציה). בדוגמה, אנחנו משתמשים בניווט כדי להעביר את הנתונים מהתכונה 'דף הבית' לעיין ברכיב התשלום באמצעות הרכיב Navigation.
navController.navigate("checkout/$bookId")
יעד הקופה מקבל מזהה ספר כארגומנט שבו הוא משתמש כדי
לאחזר מידע על הספר. אפשר להשתמש בכינוי המצב השמור כדי
מאחזרים ארגומנטים של ניווט בתוך ViewModel
של תכונת היעד.
class CheckoutViewModel(savedStateHandle: SavedStateHandle, …) : ViewModel() {
val uiState: StateFlow<CheckoutUiState> =
savedStateHandle.getStateFlow<String>("bookId", "").map { bookId ->
// produce UI state calling bookRepository.getBook(bookId)
}
…
}
אין להעביר אובייקטים כארגומנטים של ניווט. במקום זאת, יש להשתמש במזהים פשוטים שיכולות להשתמש בתכונות האלה כדי לגשת למשאבים הרצויים ולטעון אותם משכבת הנתונים. כך שומרים על צימוד נמוך ולא מפירים את מקור האמת היחיד את העיקרון.
בדוגמה הבאה, שני המודולים של התכונות תלויים באותו מודול נתונים. הזה מאפשרת לצמצם את כמות הנתונים שדרושים למודול המגשר כדי להעביר הלאה ולשמור על צימוד נמוך בין המודולים. במקום לעבור אובייקטים, המודולים צריכים להחליף מזהים פרימיטיביים ולטעון את המשאבים במודול הנתונים המשותפים.
![](https://developer.android.com/static/topic/modularization/images/2_shared_data.png?authuser=8&hl=he)
היפוך התלות
היפוך תלות הוא כאשר מארגנים את הקוד כך שההפשטה בנפרד מהטמעה קונקרטית.
- הפשטה: חוזה שמגדיר כיצד רכיבים או מודולים יוצרים אינטראקציה זו עם זו. מודולים של Abstraction מגדירים את ה-API של את המערכת, ולהכיל ממשקים ומודלים.
- הטמעה בטון: מודולים שתלויים במודול ההפשטה וליישם את ההתנהגות של הפשטה.
על מודולים שמסתמכים על ההתנהגות שמוגדרת במודול ההפשטה שתלויות בהפשטה עצמה ולא בהטמעות ספציפיות.
![](https://developer.android.com/static/topic/modularization/images/2_di_concept.png?authuser=8&hl=he)
דוגמה
נניח שמודול של תכונות זקוק למסד נתונים כדי לפעול. מודול התכונות שקשורה להטמעה של מסד הנתונים, כמו מסד נתונים מקומי של Room או למכונה המרוחקת של Firestore. השירות צריך רק לאחסן ולקרוא את נתוני האפליקציה.
לשם כך, מודול התכונות תלוי במודול ההפשטה מאשר בהטמעה של מסד נתונים ספציפי. ההפשטה הזו מגדירה את כללי השימוש של האפליקציה עם מסד הנתונים. במילים אחרות, הוא מגדיר את הכללים לאינטראקציה עם מסד נתונים. כך המודול של התכונה יכול להשתמש בכל מסד נתונים בלי שהוא יצטרך לדעת את הפרטים הבסיסיים על ההטמעה.
מודול ההטמעה הבטון מספק את ההטמעה בפועל ממשקי API שמוגדרים במודול ההפשטה. כדי לעשות את זה, תלוי גם במודול ההפשטה.
החדרת תלות
עכשיו אתם אולי תוהים איך המודול של התכונות מחובר של ההטמעה. התשובה היא החדרת תלות. התכונה לא יוצר באופן ישיר את מופע מסד הנתונים הנדרש. במקום זאת, מציין אילו יחסי תלות הם צריכים. לאחר מכן יחסי התלות האלה כלולים באופן חיצוני, בדרך כלל במודול האפליקציה.
releaseImplementation(project(":database:impl:firestore"))
debugImplementation(project(":database:impl:room"))
androidTestImplementation(project(":database:impl:mock"))
יתרונות
היתרונות של הפרדה בין ממשקי ה-API וההטמעות שלהם:
- יכולת החלפה: עם הפרדה ברורה בין API להטמעה אפשר לפתח כמה הטמעות של אותו API, ולהעביר ביניהם בלי לשנות את הקוד שמשתמש ב-API. סוג הפריט יכול להיות שימושי במיוחד בתרחישים שבהם רוצים לספק יכולות או התנהגות בהקשרים שונים. לדוגמה, לביצוע בדיקות לעומת הטמעה בפועל בסביבת הייצור.
- ביטול צימוד: ההפרדה פירושה שמודולים שמשתמשים בהפשטות לא תלויות בטכנולוגיה ספציפית. אם תבחרו לשנות את מסד הנתונים מ- מחדר ל-Firestore בהמשך, יהיה קל יותר כי השינויים קורה במודול הספציפי שמבצע את המשימה (מודול ההטמעה), להשפיע על מודולים אחרים באמצעות ה-API של מסד הנתונים.
- יכולת בדיקה: היכולת להבחין בין ממשקי API מההטמעות שלהם יכולה משמעותית לאפשר בדיקה. אפשר לכתוב מקרי בדיקה מול חוזי ה-API. אפשר להשתמש גם בהטמעות שונות כדי לבדוק תרחישים שונים ומקרי קצה שונים, כולל סימולציות של הטמעות.
- שיפור ביצועי ה-build: כשמפרידים בין ה-API במודולים שונים, שינויים בהטמעה לא מאלצות את מערכת ה-build להדר מחדש את המודולים בהתאם מודול API. כך זמני פיתוח מהירים יותר ופרודוקטיביות מוגברת. במיוחד בפרויקטים גדולים שבהם זמני הפיתוח יכולים להיות משמעותיים.
מתי צריך להפריד
מומלץ להפריד בין ממשקי ה-API לבין ההטמעות שלהם במקרים הבאים:
- יכולות מגוונות: אם אפשר להטמיע חלקים מהמערכת כמה דרכים, ממשק API ברור מאפשר החלפה בין בפועל. לדוגמה, ייתכן שיש לך מערכת עיבוד שמשתמשת ב-OpenGL או Vulkan, או מערכת חיוב שעובדת עם Play או עם החיוב הפנימי API.
- אפליקציות מרובות: אם אתם מפתחים מספר אפליקציות עם יכולות משותפות לפלטפורמות שונות, אפשר להגדיר ממשקי API נפוצים לפתח הטמעות ספציפיות לכל פלטפורמה.
- צוותים עצמאיים: ההפרדה מאפשרת למפתחים או לצוותים שונים לפעול בו-זמנית על חלקים שונים של ה-codebase. על המפתחים להתמקד להבין את חוזי ה-API ולהשתמש בהם בצורה נכונה. הם לא צריכים לדאוג לפרטי ההטמעה של מודולים אחרים.
- codebase גדול: כש-codebase גדול או מורכב, ה-API מפריד בין ה-API מההטמעה, מאפשרת לנהל את הקוד בקלות רבה יותר. הוא מאפשר לשבור את ה-codebase והופך אותן ליחידות מפורטות, מובנות וניתנות לתחזוקה.
איך מטמיעים את התקציב?
כדי ליישם את היפוך התלות, יש לבצע את השלבים הבאים:
- יצירה של מודול הפשטה: המודול הזה צריך להכיל ממשקי API (ממשקים). שמגדירים את התנהגות התכונה שלך.
- ליצור מודולים להטמעה: המודולים של ההטמעה צריכים להתבסס על
מודול API ומיישמים את ההתנהגות של הפשטה.
איור 10. המודולים להטמעה תלויים במודול הפשטה. - יצירת מודולים ברמה גבוהה שתלויים במודולים של הפשטה: במקום
בהתאם ליישום ספציפי, צור את המודולים תלויים
של מודולי הפשטה. מודולים ברמה גבוהה לא צריכים לדעת את ההטמעה
הפרטים, הם זקוקים רק לחוזה (API).
איור 11. מודולים ברמה גבוהה תלויים בהפשטות ולא בהטמעה. - מתן מודול הטמעה: לבסוף, עליכם לספק את
של יחסי התלות שלך. היישום הספציפי תלוי
הגדרת הפרויקט, אבל מודול האפליקציה הוא בדרך כלל מקום טוב לעשות זאת.
כדי לספק את היישום, עליך לציין זאת כתלות עבור
של גרסת ה-build או של קבוצת מקור לבדיקה.
איור 12. מודול האפליקציה מאפשר הטמעה בפועל.
שיטות מומלצות כלליות
כפי שציינו בהתחלה, אין דרך נכונה אחת לפתח אפליקציה מרובת מודולים. כמו שיש הרבה ארכיטקטורות תוכנה, דרכים רבות לשנות אפליקציה. עם זאת, הכללים הבאים בהמלצות יכולות לעזור לכם להפוך את הקוד לקריא יותר, קריא, בריא יותר ניתנת לבדיקה.
שמירה על עקביות בהגדרות
כל מודול מציג את התקורה של התצורה. אם מספר המודולים מגיע לסף מסוים, ניהול הגדרות עקביות הופך לאתגר. לדוגמה, חשוב שהמודולים ישתמשו ביחסי תלות של . אם אתם צריכים לעדכן מספר גדול של מודולים רק כדי להצמיד של תלות, זה לא רק מאמץ, אלא גם מקום בטעות. כדי לפתור את הבעיה, אתם יכולים להשתמש באחד מהכלים האלה כדי לפתור את הבעיה. ריכוז ההגדרות האישיות:
- קטלוגים של גרסאות הם רשימה בטוחה של סוגי תלות שנוצר על ידי Gradle במהלך הסנכרון. המקום הזה הוא מקום מרכזי שבו אפשר להצהיר על כל של יחסי התלות, וזמין לכל המודולים בפרויקט.
- משתמשים ביישומי פלאגין מקובלים כדי לשתף לוגיקת build בין מודולים.
חשיפה כמה שפחות
הממשק הציבורי של המודול צריך להיות מינימלי ולחשוף רק את
חיוניים. היא לא אמורה להדליף פרטי הטמעה חיצוניים. ההיקף
כל דבר קטן ככל האפשר. אפשר להשתמש ב-private
או ב-internal
של Kotlin
היקף החשיפה כדי שההצהרות יהיו פרטיות-מודולות. במצב הצהרה
של יחסי התלות במודול, עדיף להשתמש ב-implementation
על פני api
. האחרון
חושפת יחסי תלות מעבריביים לצרכנים של המודול. באמצעות
עשויה לקצר את זמן ה-build כי היא מפחיתה את מספר המודולים
שצריך לבנות מחדש.
העדפה ל-Kotlin & מודולים של Java
יש שלושה סוגים חיוניים של מודולים ב-Android Studio:
- מודולים של אפליקציות הם נקודת כניסה לאפליקציה. הן יכולות להכיל
קוד מקור, משאבים, נכסים ו-
AndroidManifest.xml
. הפלט של מודול האפליקציה הוא קובץ Android App Bundle (AAB) או חבילת אפליקציה ל-Android (APK). - מודולים של ספריות מכילים תוכן זהה למודולים של האפליקציה. אלו השמות משמש מודולים אחרים של Android כתלות. הפלט של מודול ספרייה הוא ארכיון Android (AAR) במבנה זהה למודולים של אפליקציה, אבל עוברים הידור לקובץ ארכיון Android (AAR) שאחר כך יוכל להשתמש בו בתור תלות. מודול ספרייה מאפשר לכלול את אותה לוגיקה ומשאבים ולהשתמש בהם במודולים רבים של אפליקציות.
- ספריות Kotlin ו-Java לא מכילות משאבים, נכסים או קובצי מניפסט.
מכיוון שמודולים של Android כוללים תקורה, רצוי להשתמש Kotlin או Java, ככל האפשר.