لایه دامنه

لایه دامنه یک لایه اختیاری است که بین لایه UI و لایه داده قرار می‌گیرد.

وقتی این لایه گنجانده شود، لایه دامنه اختیاری وابستگی‌هایی را برای لایه واسط کاربر فراهم می‌کند و به لایه داده وابسته است.
شکل ۱. نقش لایه دامنه در معماری برنامه.

لایه دامنه مسئول کپسوله‌سازی منطق پیچیده کسب‌وکار یا منطق ساده کسب‌وکار است که توسط چندین ViewModel مجدداً استفاده می‌شود. این لایه اختیاری است زیرا همه برنامه‌ها این الزامات را ندارند. فقط باید درصورت نیاز از آن استفاده کنید، مثلاً برای مدیریت پیچیدگی یا ترجیح قابلیت استفاده مجدد.

لایه دامنه مزایای زیر را ارائه می‌دهد:

  • این کار از تکرار کد جلوگیری می‌کند.
  • این کار خوانایی را در کلاس‌هایی که از کلاس‌های لایه دامنه استفاده می‌کنند بهبود می‌بخشد.
  • این کار قابلیت آزمایش برنامه را بهبود می‌بخشد.
  • با امکان تقسیم مسئولیت‌ها، از کلاس‌های بزرگ جلوگیری می‌کند.

برای اینکه این کلاس‌ها ساده و سبک بمانند، هر مورد استفاده باید فقط مسئولیت یک عملکرد را برعهده داشته باشد و نباید حاوی داده‌های تغییرپذیر باشد. درعوض، باید داده‌های تغییرپذیر را در واسط کاربر یا لایه‌های داده مدیریت کنید.

قواعد نام‌گذاری در این راهنما

در این راهنما، موارد استفاده براساس تک کنشی که مسئول آن هستند نام‌گذاری شده‌اند. قرارداد به‌صورت زیر است:

فعل در زمان حال + اسم/چه (اختیاری) + 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 نیز بستگی دارد، کلاس مورد استفاده دیگری
    که در لایه دامنه نیز قرار دارد.
شکل ۲. نمونه نمودار وابستگی برای مورد استفاده‌ای که به موارد استفاده دیگر وابسته است.

فراخوانی موارد استفاده در 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 فراخوانی کنید. چون موارد استفاده نباید حاوی داده‌های تغییرپذیر باشند، باید هربار که کلاس مورد استفاده را به‌عنوان وابستگی ارسال می‌کنید، نمونه جدیدی از آن ایجاد کنید.

بند انداختن

موارد استفاده از لایه دامنه باید 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.
شکل ۳. نمودار وابستگی برای مورد استفاده‌ای که داده‌ها را از چندین مخزن ترکیب می‌کند.

Because the logic involves multiple repositories and can become complex, you create a GetLatestNewsWithAuthorsUseCase class to abstract the logic out of the ViewModel and make it more readable. این کار همچنین باعث می‌شود منطق به‌صورت مجزا راحت‌تر آزمایش شود و در بخش‌های مختلف برنامه قابل‌استفاده مجدد باشد.

/**
 * 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 نگاشت می‌کند؛ بنابراین، اگرچه لایه داده main-safe است، این کار نباید رشته اصلی را مسدود کند زیرا نمی‌دانید چند مورد را پردازش خواهد کرد. به‌همین دلیل است که مورد استفاده، کار را بااستفاده از توزیع‌کننده پیش‌فرض به رشته پس‌زمینه منتقل می‌کند.

مصرف‌کنندگان دیگر

جدااز لایه میانای کاربر، لایه دامنه می‌تواند توسط کلاس‌های دیگر مانند سرویس‌ها و کلاس Application مجدداً استفاده شود. علاوه‌براین، اگر پلاتفرم‌های دیگر مثل TV یا Wear پایگاه کد مشترکی با برنامه تلفن همراه داشته باشند، لایه میانای کاربر آن‌ها نیز می‌تواند از موارد استفاده مجدداً استفاده کند تا از همه مزایای لایه دامنه که در بالا ذکر شد بهره‌مند شود.

محدودیت دسترسی به لایه داده

One other consideration when implementing the domain layer is whether you should still allow direct access to the data layer from the UI layer, or force everything through the domain layer.

لایه UI نمی‌تواند مستقیماً به لایه داده دسترسی داشته باشد، باید ازطریق لایه «دامنه» انجام شود
شکل ۴. نمودار وابستگی که نشان می‌دهد دسترسی لایه واسط کاربر به لایه داده رد شده است.

یکی از مزایای ایجاد این محدودیت این است که از دور زدن منطق لایه دامنه توسط UI شما جلوگیری می‌کند، برای مثال، اگر درحال انجام ثبت گزارش تجزیه‌وتحلیل در هر درخواست دسترسی به لایه داده هستید.

بااین‌حال، نقطه ضعف بالقوه مهم این است که شما را مجبور می‌کند موارد استفاده را اضافه کنید، حتی زمانی که آن‌ها فقط فراخوانی‌های تابع ساده به لایه داده هستند، که می‌تواند پیچیدگی را با مزیت کمی اضافه کند.

رویکرد خوب این است که موارد استفاده را فقط درصورت نیاز اضافه کنید. اگر متوجه شدید که لایه میانای کاربر شما تقریباً منحصراً ازطریق موارد استفاده به داده‌ها دسترسی دارد، ممکن است منطقی باشد که فقط از این طریق به داده‌ها دسترسی داشته باشید.

درنهایت، تصمیم‌گیری درباره محدود کردن دسترسی به لایه داده به پایگاه کد شما بستگی دارد و اینکه آیا قوانین سخت‌گیرانه را ترجیح می‌دهید یا رویکرد انعطاف‌پذیرتر را.

آزمایش

راهنمای آزمایش عمومی هنگام آزمایش لایه دامنه اعمال می‌شود. برای سایر آزمایش‌های میانای کاربر، توسعه‌دهندگان معمولاً از مخزن‌های جعلی استفاده می‌کنند، و استفاده از مخزن‌های جعلی هنگام آزمایش لایه دامنه نیز روش خوبی است.

نمونه‌ها

نمونه‌های Google زیر نحوه استفاده از لایه دامنه را نشان می‌دهند. برای دیدن این راهنمایی در عمل، آن‌ها را کاوش کنید: