طبقة النطاق

طبقة النطاق هي طبقة اختيارية تقع بين طبقة واجهة المستخدم وطبقة البيانات.

وعند تضمين هذه الطبقة، توفّر طبقة النطاق الاختيارية تبعيات لطبقة واجهة المستخدم وتعتمد على طبقة البيانات.
الشكل 1. دور طبقة النطاق في بنية التطبيق.

تكون طبقة النطاق مسؤولة عن تغليف منطق العمل المعقد أو منطق الأعمال البسيط الذي يُعاد استخدامه من خلال نماذج عرض متعددة. هذه الطبقة اختيارية لأنه لن تكون هذه المتطلبات لجميع التطبيقات. يجب عليك استخدامه فقط عند الحاجة - على سبيل المثال، للتعامل مع التعقيد أو أفضلية إعادة الاستخدام.

توفر طبقة النطاق المزايا التالية:

  • يتجنّب تكرار الرموز.
  • يحسن سهولة القراءة في الفئات التي تستخدم فئات طبقة النطاق.
  • يحسن قابلية اختبار التطبيق.
  • يتجنب الفصول الكبيرة من خلال السماح لك بتقسيم المسئوليات.

للحفاظ على بساطة هذه الفئات وخفتها، يجب أن تتحمل كل حالة استخدام المسئولية فقط بشأن وظيفة واحدة، ويجب ألا تحتوي على بيانات قابلة للتغيير. بدلاً من ذلك، عليك معالجة البيانات القابلة للتغيير في واجهة المستخدم أو طبقات البيانات.

اصطلاحات التسمية في هذا الدليل

في هذا الدليل، تتم تسمية حالات الاستخدام بعد الإجراء الوحيد المسئول عنه. والاصطلاح هو كما يلي:

فعل في زمن المضارع + اسم/ماذا (اختياري) + UseCase

على سبيل المثال: FormatDateUseCase أو LogOutUserUseCase أو GetLatestNewsWithAuthorsUseCase أو MakeLoginRequestUseCase

التبعيات

في بنية التطبيق النموذجية، استخدِم فئات الحالة المناسبة بين ViewModels من طبقة واجهة المستخدم والمستودعات من طبقة البيانات. وهذا يعني أن استخدام فئات الحالة يعتمد عادةً على فئات المستودع، وتتواصل مع طبقة واجهة المستخدم بنفس طريقة عمل المستودعات، باستخدام إما استدعاءات (لـ Java) أو الكوروتينات (لـ 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)
    }
}

في هذا المثال، تسمح لك الطريقة invoke() في FormatDateUseCase باستدعاء مثيلات الفئة كما لو كانت دوال. لا تقتصر الطريقة invoke() على أي توقيع محدّد، إذ يمكن أن تتضمّن أي عدد من المعلَمات وتعرض أي نوع. يمكنك أيضًا تحميل invoke() بتوقيعات مختلفة في الصف. يمكنك استدعاء حالة الاستخدام من المثال أعلاه على النحو التالي:

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

لمزيد من المعلومات حول عامل التشغيل invoke()، يُرجى الاطّلاع على مستندات Kotlin.

مراحل النشاط

ليس لحالات الاستخدام دورة حياة خاصة. بدلاً من ذلك، يتم إسنادها إلى الفصل الذي تستخدمه. وهذا يعني أنّه يمكنك طلب حالات الاستخدام من الفئات في طبقة واجهة المستخدم أو من الخدمات أو من فئة Application نفسها. نظرًا لأن حالات الاستخدام لا ينبغي أن تحتوي على بيانات قابلة للتغيير، يجب عليك إنشاء مثيل جديد لفئة حالة استخدام في كل مرة تمررها فيها كتبعية.

سلسلة المحادثات

يجب أن تكون حالات الاستخدام من طبقة النطاق آمنة؛ أي يجب أن تكون آمنة للاتصال من سلسلة التعليمات الرئيسية. إذا كانت فئات حالة الاستخدام تؤدي عمليات حظر طويلة المدى، تكون مسؤولة عن نقل هذا المنطق إلى سلسلة التعليمات المناسبة. ومع ذلك، قبل القيام بذلك، تحقق مما إذا كانت عمليات الحظر هذه سيتم وضعها بشكل أفضل في طبقات أخرى من التسلسل الهرمي. عادةً ما تحدث العمليات الحسابية المعقدة في طبقة البيانات للتشجيع على إعادة الاستخدام أو التخزين المؤقت. على سبيل المثال، من الأفضل وضع العملية التي تستهلك موارد كثيرة في قائمة كبيرة في طبقة البيانات مقارنةً بطبقة النطاق إذا كانت النتيجة بحاجة إلى تخزينها مؤقتًا لإعادة استخدامها على شاشات متعددة من التطبيق.

يعرض المثال التالي حالة استخدام تؤدي عملها على سلسلة ترابط في الخلفية:

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، لذلك على الرغم من أنّ طبقة البيانات آمنة بشكل رئيسي، يجب ألّا تحظر سلسلة التعليمات الرئيسية هذه لأنّك لا تعرف عدد العناصر التي ستتم معالجتها. لهذا السبب تنقل حالة الاستخدام العمل إلى سلسلة خلفية باستخدام المرسل الافتراضي.

مستهلكون آخرون

وبصرف النظر عن طبقة واجهة المستخدم، يمكن إعادة استخدام طبقة النطاق من خلال فئات أخرى مثل الخدمات وفئة Application. علاوة على ذلك، إذا كانت الأنظمة الأساسية الأخرى مثل التلفزيون أو Wear تشارك قاعدة الترميز مع تطبيق الجوّال، يمكن لطبقة واجهة المستخدم أيضًا إعادة استخدام حالات الاستخدام للحصول على كل المزايا المذكورة أعلاه لطبقة النطاق.

تقييد الوصول إلى طبقة البيانات

أحد الاعتبارات الأخرى عند تنفيذ طبقة النطاق هو ما إذا كان ينبغي عليك الاستمرار في السماح بالوصول المباشر إلى طبقة البيانات من طبقة واجهة المستخدم، أو فرض كل شيء من خلال طبقة النطاق.

لا يمكن لطبقة واجهة المستخدم الوصول إلى طبقة البيانات مباشرةً، بل يجب أن تمر عبر طبقة النطاق
الشكل 4. رسم بياني للتبعية يعرض حظر وصول طبقة واجهة المستخدم إلى طبقة البيانات.

تتمثل إحدى ميزات التقييد في أنّه يمنع واجهة المستخدم من تجاوز منطق طبقة النطاق، على سبيل المثال، إذا كنت تُسجل بيانات الإحصاءات لكل طلب وصول إلى طبقة البيانات.

ومع ذلك، فإنّ العيب المحتمل الذي قد يكون مهمًا هو أنّه يفرض عليك إضافة حالات استخدام حتى عندما تكون مجرد استدعاءات دالة بسيطة في طبقة البيانات، ما قد يزيد من التعقيد بدون فائدة.

من الأساليب الجيدة إضافة حالات استخدام عند الضرورة فقط. إذا وجدت أن طبقة واجهة المستخدم تصل إلى البيانات من خلال حالات الاستخدام على نحو حصري تقريبًا، فقد يكون من المنطقي الوصول فقط إلى البيانات بهذه الطريقة.

في النهاية، يعود قرار تقييد الوصول إلى طبقة البيانات إلى قاعدة التعليمات البرمجية الفردية، وما إذا كنت تفضّل قواعد صارمة أو نهجًا أكثر مرونة.

الاختبار

تسري إرشادات الاختبار العامة عند اختبار طبقة النطاق. بالنسبة إلى اختبارات واجهة المستخدم الأخرى، يستخدم المطورون عادةً مستودعات مزيفة، ومن الممارسات الجيدة استخدام مستودعات مزيفة عند اختبار طبقة النطاق أيضًا.

عيّنات

توضح نماذج Google التالية استخدام طبقة المجال. يمكنك الانتقال إلى هذه الصفحة للاطّلاع على هذه الإرشادات عمليًا: