שכבת דומיין

שכבת הדומיין היא שכבה אופציונלית שנמצאת בין שכבת ממשק המשתמש בשכבת הנתונים.

כששכבת הדומיין האופציונלית נכללת, היא מספקת יחסי תלות
    בשכבת ה-UI, ותלויה בשכבת הנתונים.
איור 1. התפקיד של שכבת הדומיין בארכיטקטורת אפליקציות.

שכבת הדומיין אחראית לעקיפת לוגיקה עסקית מורכבת, או לוגיקה עסקית פשוטה שבה עושים שימוש חוזר במספר מודלים של ViewModel. השכבה הזאת אופציונלי, כי לא לכל האפליקציות יהיו את הדרישות האלה. צריך להשתמש רק במקרה הצורך – לדוגמה, כדי לטפל במורכבות או בהעדפה של שימוש חוזר.

שכבת דומיין מספקת את היתרונות הבאים:

  • כך נמנעים כפילויות בקוד.
  • הוא משפר את הקריאוּת במחלקות שמשתמשות במחלקות של שכבות דומיין.
  • הוא משפר את יכולת הבדיקה של האפליקציה.
  • כדי למנוע קבוצות גדולות, אפשר לחלק את תחומי האחריות.

כדי שהמחלקות האלה יהיו פשוטות וקלות, כל תרחיש לדוגמה צריך לכלול אחריות על פונקציונליות יחידה, והן לא יכולות לכלול . במקום זאת, צריך לטפל בנתונים שניתנים לשינוי בממשק המשתמש או בשכבות הנתונים.

מוסכמות מתן שמות במדריך הזה

במדריך הזה, התרחישים לדוגמה נקראים על שם הפעולה הבודדת שהם אחראים לכך עבור. המוסכמה היא:

פועל בזמן נוכחי + שם עצם/מה (אופציונלי) + תרחיש לדוגמה.

לדוגמה: FormatDateUseCase, LogOutUserUseCase, GetLatestNewsWithAuthorsUseCase או MakeLoginRequestUseCase.

יחסי תלות

בארכיטקטורה טיפוסית של אפליקציות, סיווגי התרחישים לדוגמה מתאימים בין ViewModels השכבה UI והמאגרים משכבת הנתונים. כלומר, סיווגי תרחישים לדוגמה בדרך כלל תלויות במחלקות המאגר, והן מתקשרות עם שכבת ממשק המשתמש כמו במאגרים Kotlin). למידע נוסף על הנושא, אפשר לעיין בשכבת הנתונים .

לדוגמה, ייתכן שבאפליקציה שלכם יש מחלקה של תרחיש לדוגמה שמאחזרת נתונים מ מאגר חדשות ומאגר מחברים, ומשלב אותם:

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository
) { /* ... */ }

התרחישים לדוגמה כוללים לוגיקה לשימוש חוזר, ולכן אפשר להשתמש בהם גם בדרכים אחרות במקרים שונים. הגיוני שיש כמה רמות של תרחישים לדוגמה בשכבת הדומיין. עבור תרחיש לדוגמה שמוגדר בדוגמה הבאה יכול להשתמש תרחיש לדוגמה של FormatDateUseCase אם כמה כיתות משכבת ממשק המשתמש מסתמכות על זמן כדי להציג את ההודעה המתאימה במסך:

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
GetLatestNewsWithAuthorsUseCase תלויה במחלקות המאגרים
    בשכבת הנתונים, אבל היא תלויה גם ב-FormatDataUseCase, סוג נוסף של תרחיש לדוגמה
    שנמצא גם בשכבת הדומיין.
איור 2. תרשים תלות לדוגמה לתרחיש לדוגמה שתלוי במודל תלות במקרים שונים.

תרחישים לדוגמה של שיחות ב-Kotlin

ב-Kotlin אפשר להגדיר קריאה למופעים של תרחיש לדוגמה כפונקציות באמצעות הגדרת הפונקציה invoke() עם מקש הצירוף operator. כדאי לעיין בנושאים הבאים דוגמה:

class FormatDateUseCase(userRepository: UserRepository) {

    private val formatter = SimpleDateFormat(
        userRepository.getPreferredDateFormat(),
        userRepository.getPreferredLocale()
    )

    operator fun invoke(date: Date): String {
        return formatter.format(date)
    }
}

בדוגמה הזו, ה-method invoke() ב-FormatDateUseCase מאפשרת של המחלקה, כאילו הם פונקציות. השיטה invoke() היא לא מוגבלת לכל חתימה ספציפית – היא יכולה לכלול כל מספר של פרמטרים. ומחזירה כל טיפוס. אפשר גם לבצע עומס יתר על invoke() עם חתימות שונות בכיתה שלכם. אפשר לקרוא לתרחיש לדוגמה מהדוגמה שלמעלה כך:

class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
    init {
        val today = Calendar.getInstance()
        val todaysDate = formatDateUseCase(today)
        /* ... */
    }
}

מידע נוסף על האופרטור invoke() זמין ב-Kotlin מסמכים.

מחזור חיים

לתרחישים לדוגמה אין מחזור חיים משלהם. במקום זאת, הם מוגבלים לכיתה שמשתמש בהם. כלומר, אפשר לבצע קריאה לתרחישים לדוגמה דרך כיתות בממשק המשתמש. בשכבת זרימת הנתונים, מהשירותים או מהמחלקה Application עצמה. כי תרחישים לדוגמה לא אמורה להכיל נתונים שניתנים לשינוי, עליכם ליצור מופע חדש של תרחיש לדוגמה. בכל פעם שאתם מעבירים אותו בתור תלות.

הברגה

תרחישים לדוגמה משכבת הדומיין חייבים להיות main-safe; במילים אחרות, הם צריכים אפשר לבצע שיחות מהשרשור הראשי. אם התרחישים לדוגמה מעובדים לאורך זמן של חסימה, הם אחראים להעביר את הלוגיקה הזו את השרשור המתאים. עם זאת, לפני שאתם עושים את זה, בדקו אם הם חוסמים יוצגו טוב יותר בשכבות אחרות בהיררכיה. בדרך כלל, מתרחשות חישובים מורכבים בשכבת הנתונים כדי לעודד שימוש חוזר שמירה במטמון. לדוגמה, עדיף לבצע פעולה שצורכת משאבים רבים על רשימה גדולה בשכבת הנתונים מאשר בשכבת הדומיין, אם התוצאה שנשמר במטמון כדי לעשות בו שימוש חוזר בכמה מסכים באפליקציה.

בדוגמה הבאה מוצג תרחיש לדוגמה שמבצע את פעולתו ברקע שרשור:

class MyUseCase(
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {

    suspend operator fun invoke(...) = withContext(defaultDispatcher) {
        // Long-running blocking operations happen on a background thread.
    }
}

משימות נפוצות

בקטע הזה נסביר איך לבצע משימות נפוצות בשכבת הדומיין.

לוגיקה עסקית פשוטה לשימוש חוזר

אתם צריכים להרכיב לוגיקה עסקית ניתנת לחזרה שנמצאת בשכבת ממשק המשתמש מחלקה של התרחיש לדוגמה. כך יהיה קל יותר להחיל שינויים בכל מקום שבו הלוגיקה נעשה שימוש. היא גם מאפשרת לבדוק את הלוגיקה בנפרד.

עיינו בדוגמה FormatDateUseCase שתוארה קודם לכן. אם העסק שלכם דרישות בנוגע לשינוי פורמט התאריך בעתיד, צריך רק לשנות את הקוד במקום אחד מרכזי.

שילוב מאגרים

אפליקציית חדשות יכולה לכלול NewsRepository ו-AuthorsRepository כיתות שמטפלים בפעולות שקשורות לחדשות ולנתונים של מחברים, בהתאמה. הכיתה Article ש-NewsRepository שחושף מכיל רק את שם המחבר, אבל אתה רוצה כדי להציג מידע נוסף על המחבר במסך. פרטי המחבר של AuthorsRepository.

GetLatestNewsWithAuthorsUseCase תלויה בשני מאגרים שונים
    מחלקות משכבת הנתונים: NewsRepository ו-AuthorsRepository.
איור 3. תרשים תלות לתרחיש לדוגמה שמשלב נתונים מתוך יותר ממאגרים שונים.

מכיוון שהלוגיקה כוללת מאגרים מרובים ועשויה להיות מורכבת, ליצור מחלקה GetLatestNewsWithAuthorsUseCase כדי להפשט את הלוגיקה את ViewModel ולהפוך אותו לקריא יותר. זה גם הופך את הלוגיקה לקלה יותר, יכולים להיות מבודדים, ולהשתמש בהם שוב בחלקים שונים של האפליקציה.

/**
 * This use case fetches the latest news and the associated author.
 */
class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    suspend operator fun invoke(): List<ArticleWithAuthor> =
        withContext(defaultDispatcher) {
            val news = newsRepository.fetchLatestNews()
            val result: MutableList<ArticleWithAuthor> = mutableListOf()
            // This is not parallelized, the use case is linearly slow.
            for (article in news) {
                // The repository exposes suspend functions
                val author = authorsRepository.getAuthor(article.authorId)
                result.add(ArticleWithAuthor(article, author))
            }
            result
        }
}

הלוגיקה ממפה את כל הפריטים ברשימה news. למרות ששכבת הנתונים ראשי, עבודה זו לא אמורה לחסום את ה-thread הראשי כי לא ידוע לך איך הרבה פריטים שהוא יעבד. לכן בתרחיש לדוגמה מעבירים את העבודה לרקע, באמצעות כלי העריכה שמוגדר כברירת מחדל.

צרכנים אחרים

חוץ משכבת ממשק המשתמש, אפשר לעשות שימוש חוזר בשכבת הדומיין על ידי מחלקות אחרות כמו והמחלקה Application. בנוסף, אם בפלטפורמות אחרות כמו טלוויזיה או Wear משותף עם האפליקציה לנייד, שכבת ממשק המשתמש שלהם יכולה גם לעשות שימוש חוזר כדי לקבל את כל היתרונות של שכבת הדומיין.

הגבלת הגישה לשכבת הנתונים

שיקול אחד נוסף בזמן הטמעת שכבת הדומיין הוא אם עדיין לאפשר גישה ישירה לשכבת הנתונים משכבת הנתונים, או לאלץ הכול דרך שכבת הדומיין.

שכבת ממשק המשתמש לא יכולה לגשת ישירות לשכבת הנתונים, היא חייבת לעבור דרך שכבת הדומיין
איור 4. תרשים תלות שמציג את שכבת ממשק המשתמש שהגישה אליה נדחתה בשכבת הנתונים.

היתרון של ההגבלה הזו הוא שההגבלה מונעת מהמערכת לעקוף את המגבלה הלוגיקה של שכבת הדומיין. לדוגמה, אם אתם מבצעים רישום ביומן של ניתוח נתונים לשכבת הנתונים.

עם זאת, החסרון הפוטנציאלי המשמעותי הוא שהוא מאלץ אתכם מוסיפים תרחישים לדוגמה, גם כשהם רק הפעלות של פונקציות פשוטות לשכבת הנתונים, מצב כזה עלול להוסיף מורכבות מבחינתכם, באופן מזערי.

כדאי להוסיף תרחישים לדוגמה רק כשיש צורך. אם מתברר שממשק המשתמש ניגשת לנתונים כמעט אך ורק באמצעות תרחישים לדוגמה, היא עשויה כדאי לגשת רק לנתונים בצורה הזו.

בסופו של דבר, ההחלטה להגביל את הגישה לשכבת הנתונים תלויה קוד בסיסי ספציפי, ואם אתם מעדיפים כללים מחמירים או גמישים יותר .

בדיקה

כשבודקים את הדומיין חלים הנחיות לבדיקה כללית בשכבת זרימת הנתונים. בבדיקות אחרות של ממשק המשתמש, מפתחים בדרך כלל משתמשים במאגרים מזויפים, מומלץ להשתמש במאגרים מזויפים גם כשבודקים את שכבת הדומיין.

דוגמיות

הדוגמאות הבאות של Google מדגימות את השימוש בשכבת הדומיין. מומלץ לעיין בהם כדי לראות את ההנחיות האלה בפועל: