لایه دامنه

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

هنگامی که شامل می شود، لایه دامنه اختیاری وابستگی هایی را به لایه UI ارائه می دهد و به لایه داده بستگی دارد.
شکل 1. نقش لایه دامنه در معماری برنامه.

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

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

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

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

قراردادهای نامگذاری در این راهنما

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

فعل در زمان حال + اسم/چه (اختیاری) + UseCase .

به عنوان مثال: FormatDateUseCase ، LogOutUserUseCase ، GetLatestNewsWithAuthorsUseCase ، یا MakeLoginRequestUseCase .

وابستگی ها

در یک معماری برنامه معمولی، از کلاس‌های case مناسب بین ViewModels از لایه UI و مخازن از لایه داده استفاده کنید. این بدان معناست که کلاس‌های use case معمولاً به کلاس‌های مخزن بستگی دارند و با لایه UI به همان شیوه مخازن ارتباط برقرار می‌کنند – با استفاده از callback (برای جاوا) یا coroutine (برای Kotlin). برای کسب اطلاعات بیشتر در مورد این، به صفحه لایه داده مراجعه کنید.

به عنوان مثال، در برنامه خود، ممکن است یک کلاس use case داشته باشید که داده ها را از یک مخزن اخبار و یک مخزن نویسنده واکشی می کند و آنها را با هم ترکیب می کند:

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

از آنجا که موارد استفاده حاوی منطق قابل استفاده مجدد هستند، می توانند توسط موارد استفاده دیگر نیز استفاده شوند. طبیعی است که چندین سطح از موارد استفاده در لایه دامنه وجود داشته باشد. به عنوان مثال، مورد استفاده تعریف شده در مثال زیر می تواند از حالت استفاده FormatDateUseCase استفاده کند اگر چندین کلاس از لایه UI برای نمایش پیام مناسب روی صفحه به مناطق زمانی متکی باشند:

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
GetLatestNewsWithAuthorsUseCase به کلاس های مخزن از لایه داده بستگی دارد، اما به FormatDataUseCase نیز بستگی دارد، کلاس استفاده دیگری که در لایه دامنه نیز وجود دارد.
شکل 2. نمودار وابستگی مثال برای یک مورد استفاده که به موارد استفاده دیگر بستگی دارد.

موارد استفاده از تماس در کاتلین

در Kotlin، می‌توانید نمونه‌های کلاس case را به‌عنوان توابع با تعریف تابع 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 مراجعه کنید.

چرخه زندگی

موارد استفاده چرخه عمر خود را ندارند. درعوض، آنها به کلاسی که از آنها استفاده می کند، اختصاص داده می شوند. این بدان معنی است که می توانید موارد استفاده را از کلاس های لایه UI، از سرویس ها یا از خود کلاس Application فراخوانی کنید. از آنجایی که موارد استفاده نباید حاوی داده‌های قابل تغییر باشند، باید هر بار که آن را به عنوان وابستگی ارسال می‌کنید، یک نمونه جدید از کلاس use case ایجاد کنید.

نخ زنی

موارد استفاده از لایه دامنه باید main-safe باشد. به عبارت دیگر، آنها باید امن باشند تا از موضوع اصلی تماس بگیرند. اگر کلاس‌های use case عملیات مسدودسازی طولانی‌مدت را انجام می‌دهند، مسئولیت انتقال آن منطق به رشته مناسب را بر عهده دارند. با این حال، قبل از انجام این کار، بررسی کنید که آیا آن عملیات مسدودکننده بهتر است در سایر لایه‌های سلسله مراتب قرار گیرند. به طور معمول، محاسبات پیچیده در لایه داده انجام می شود تا قابلیت استفاده مجدد یا ذخیره سازی را تشویق کند. به عنوان مثال، اگر نتیجه برای استفاده مجدد از آن در چندین صفحه برنامه نیاز به ذخیره سازی در حافظه پنهان داشته باشد، یک عملیات فشرده منابع در یک لیست بزرگ در لایه داده بهتر از لایه دامنه قرار می گیرد.

مثال زیر یک مورد استفاده را نشان می دهد که کار خود را بر روی یک رشته پس زمینه انجام می دهد:

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

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

وظایف مشترک

این بخش نحوه انجام وظایف رایج لایه دامنه را توضیح می دهد.

منطق تجاری ساده قابل استفاده مجدد

شما باید منطق تجاری قابل تکرار موجود در لایه UI را در یک کلاس use case کپسوله کنید. این کار اعمال هر گونه تغییر در هر جایی که از منطق استفاده می شود آسان تر می کند. همچنین به شما این امکان را می دهد که منطق را به صورت مجزا آزمایش کنید.

مثال 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 را ترسیم می کند. بنابراین حتی اگر لایه داده ایمن اصلی است، این کار نباید رشته اصلی را مسدود کند زیرا نمی دانید چند مورد پردازش می شود. به همین دلیل است که use case کار را با استفاده از دیسپچر پیش‌فرض به یک رشته پس‌زمینه منتقل می‌کند.

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

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

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

یکی دیگر از ملاحظات هنگام پیاده‌سازی لایه دامنه این است که آیا هنوز باید اجازه دسترسی مستقیم به لایه داده از لایه UI را بدهید یا همه چیز را مجبور به عبور از لایه دامنه کنید.

لایه UI نمی تواند مستقیماً به لایه داده دسترسی داشته باشد، باید از لایه Domain عبور کند
شکل 4. نمودار وابستگی نشان می دهد که لایه UI از دسترسی به لایه داده محروم است.

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

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

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

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

تست کردن

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

نمونه ها

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

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}